Event Channels are defined 1 as being an object that mediates the transfer of CORBA events between producers and consumers. These CORBA events are translated at runtime dynamically into various structures and primitives based on an XML definition, and the transfer is a Remote Procedure Call (RPC).
This translation gives events flexibility at the cost of some performance. In the following sections, the flexibility of events and event channels will be explored with a variety of scenarios covering how to monitor, tap, and drive event channels.
Event Viewer
REDHAWK includes a console application, eventviewer
, for monitoring event channels — both system and user-defined. Since Event Channels are constrained to a Domain, the Domain name must be known:
eventviewer [options]
For channels that have not been registered yet (by FindBy
in a Node or Waveform) can be created forcefully by using the -f
option. The output of this tool is provided in the following sections along with other examples for tracking events.
Domain-Level Channels
The REDHAWK Domain Manager is responsible for maintaining the system state. It must know what entities exist in the Domain as well as the present state of each. To support maintenance of this system-wide view are the ODM and IDM channels, Outgoing and Incoming Domain Management channels, respectively. In each case, the direction provides a clue as to what types of messages to expect in the channel.
The ODM Channel
This channel contains events outbound from the Domain. Primarily these events include startup and shutdown announcements for entities registering and unregistering with the Domain Manager, or correspondingly, the addition and removal of those entities.
The event details include producerId
, sourceId
, and sourceName
. The producerId
is always the Domain Manager generating the Event, and the sourceId
is the entity directly responsible for causing the Event. See the following example for more on these details.
The IDM Channel
Opposite to the ODM channel, the IDM channel are events going into the Domain. These events are often accompanied by a category and change type. Some examples are state-changes: Device idle, active, busy, etc. Several additional examples can be found in Section 16 of the REDHAWK online documentation.
In combination then, one could observe the registration of a Component on the ODM channel, and observe property changes on the IDM channel (if configured in the Waveform). Another combination would be to monitor for a required Device to register and then automatically launch (by the Python shell) a Waveform known to require a Device of that type.
Life Events Example for a Node
Recall that a Node (Device Manager) has the responsibility of managing both Devices and Services within its context. It is therefore the first entity to come online and the last to leave with respect to that context.
The launching and running phases are almost exclusively generate events in the ODM and IDM Channel, respectively. During a shutdown, both channels receive related events as the Entity tries to provide the Domain with some warning about its departure.
Startup
The Node (Device Manager) launch process also creates its child entities (Devices and Services). Each child entity then also registers with the Domain to indicate all available resources managed by that Node.
On the ODM Channel
For example, a Node called rapsberry_pi1b
manages three additional Devices: GPP_1
, rtl_sdr_device_1
, gps_receiver_1
. First, the Node (Device Manager) is announced:
{'sourceId': 'DCE:1b67ae18-914b-4fc7-bfe5-4d533693d44f', 'sourceName': 'raspberry_pi1b', 'sourceIOR':, 'sourceCategory': DEVICE_MANAGER, 'producerId': 'DCE:9ae444e0-0bfd-4e3d-b16c-1cffb3dc0f46'}
Once the Node is launched, its associated Devices that successfully launched are also announced on the ODM Channel. The following is an example of the Node’s GPP_1
registering:
{'sourceId': 'raspberry_pi1b:GPP_ID', 'sourceName': 'GPP_1', 'sourceIOR':, 'sourceCategory': DEVICE, 'producerId': 'DCE:9ae444e0-0bfd-4e3d-b16c-1cffb3dc0f46'}
Since the Domain Manager produced the event, its ID is set as the producerId
for each Event. The remaining source*
details are specific to the entity that caused the Event.
Ongoing Use
During ongoing use, entities may announce internal state changes as each is started, stopped, or tasked. Continuing with the Node example, the Devices will announce changes to the Usage State (IDLE
, ACTIVE
, or BUSY
), for example:
{'sourceId': 'raspberry_pi1b:GPS_ID',
'stateChangeCategory': USAGE_STATE_EVENT,
'stateChangeFrom': IDLE,
'stateChangeTo': IDLE,
'producerId': 'raspberry_pi1b:GPS_ID'}
Other stateChangeCategory
types include the following with the possible enumeration of each:
- Administrative
LOCKED
UNLOCKED
SHUTTING_DOWN
- Operational
ENABLED
DISABLED
Naturally, if SHUTTING_DOWN
is emitted, one can expect the associated entity’s ID will appear on the ODM Channel having been announced by the Domain Manager as departing the system.
Shutdown
Recall that a Device Manager’s Devices and Services may be associated to a Waveform’s Components to either execute that Component or connect to one of its ports. These associations are generally called requirements 2 and are managed by the Domain’s Application Factory instance(s). On shutdown, the ODM and IDM Channels convey Events that might impact those requirements.
As one might expect, changes in the states of an entity occur before the removal of that entity. Likewise, an entity’s departure will be announced first on the IDM and then on the ODM Channel.
On the IDM Channel
The Events pushed through the IDM Channel on shutdown are to assist the Domain Manager in the graceful shutdown of any entities that might rely on the associated entity. The usage state will likely change, but perhaps more importantly the administrative state will cycle down for the cleanup.
These announcements also assist with automatically releasing a Waveform if its implementation requirements 3 will become broken. For example, the GPS device shutting down emits these changes. First a gratuitous usage state change:
{'sourceId': 'raspberry_pi1b:GPS_ID',
'stateChangeCategory': USAGE_STATE_EVENT,
'stateChangeFrom': IDLE,
'stateChangeTo': IDLE,
'producerId': 'raspberry_pi1b:GPS_ID'}
Then the transition away from UNLOCKED
(usable) occurs:
{'sourceId': 'raspberry_pi1b:GPS_ID',
'stateChangeCategory': ADMINISTRATIVE_STATE_EVENT,
'stateChangeFrom': UNLOCKED,
'stateChangeTo': SHUTTING_DOWN,
'producerId': 'raspberry_pi1b:GPS_ID'}
{'sourceId': 'raspberry_pi1b:GPS_ID',
'stateChangeCategory': ADMINISTRATIVE_STATE_EVENT,
'stateChangeFrom': SHUTTING_DOWN,
'stateChangeTo': LOCKED,
'producerId': 'raspberry_pi1b:GPS_ID'}
It is the entity’s administrative state transition into SHUTTING_DOWN
and then LOCKED
which closes out the Device from being assigned any additional tasks or connections as it proceeds to unregister.
On the ODM Channel
Once an entity has effectively stopped and cleaned up its state (or failed), the higher-level unregistering events occur on the ODM Channel. First the Devices unregister (for example, the GPP_1
):
{'sourceId': 'raspberry_pi1b:GPP_ID',
'sourceName': 'GPP_1',
'sourceCategory': DEVICE,
'producerId': 'DCE:9ae444e0-0bfd-4e3d-b16c-1cffb3dc0f46'}
Once the entities owned by the Node have unregistered, the Node unregisters:
{'sourceId': 'DCE:1b67ae18-914b-4fc7-bfe5-4d533693d44f',
'sourceName': 'raspberry_pi1b',
'sourceCategory': DEVICE_MANAGER,
'producerId': 'DCE:9ae444e0-0bfd-4e3d-b16c-1cffb3dc0f46'}
With the Node being in charge of managing its entities, the order of each set of Events makes sense. The Node was the first entity to come online and is now the last to leave.
Listener Code Example
The ODM and IDM Channels both have very useful information for overall system administration and automation. Where REDHAWK’s automation ends, a user’s can begin. But first, one must access the channels.
The Code
Included with REDHAWK is a Python-based interface to most of the system. Organized under the ossie.utils
package, several useful interfaces exist. The following is a short example which includes a class that monitors the ODM Channel for Device Managers being added and removed.
from ossie.utils import redhawk
from ossie.utils.redhawk.channels import ODMListener
from ossie.utils.weakobj import WeakBoundMethod
import gevent, signal
Note: prior to REDHAWK 2.0,
weakobj
wasweakmethod
(which is now deprecated).
The first is for scanning for and attaching to a REDHAWK Domain. The second two are for creating and binding an ODM Listener the various possible ODM events that will be observed. The final pair of imports are going to be used for concurrent processing and termination of the class’s run loop.
class MyListener():
def __init__(self):
self.odm = ODMListener()
dom = redhawk.attach(redhawk.scan()[0])
self.odm.connect(dom)
self.odm.deviceManagerAdded.addListener(
WeakBoundMethod(self.add))
self.odm.deviceManagerRemoved.addListener(
WeakBoundMethod(self.remove))
while True:
print("Waiting for events...")
gevent.sleep(1)
def __del__(self):
self.odm.disconnect()
Upon creation, the MyListener
class will attach 4 to the first Domain 5 it finds and then connect to that Domain’s ODM Channel using the ODMListener
.
Next, the WeakBoundMethod
is used to redirect the deviceManagerAdded
and deviceManagerRemoved
notifications into its own add
and remove
callbacks. It then enters an infinite loop, pausing each second, as it waits for events to occur.
For cleanly exiting, the __del__
method calls disconnect on the ODM Listener instance.
def add(self, odm_event):
print("Added ID: {0}".format(odm_event.sourceId))
def remove(self, odm_event):
print("Removed ID: {0}".format(odm_event.sourceId))
if __name__ == '__main__':
ge = gevent.spawn(MyListener)
gevent.signal(signal.SIGINT, ge.kill)
print("CTRL+C to exit.")
ge.join()
The add
and remove
callbacks simply print a string indicating the ID that was added or removed. The main function spawns a Greenlet, binds the SIGINT (CTRL+C
) interruption to killing the Greenlet. It then blocks at join()
until the Greenlet exits.
Testing the Class
Testing the MyListener
class is as simple as its design. Launch it and then start and stop Device Managers. A console log would look something like the following:
$ python odmtest.py
CTRL+C to exit.
Waiting for events...
Waiting for events...
Waiting for events...
Added ID: DCE:1b67ae18-914b-4fc7-bfe5-4d533693d44f
Waiting for events...
Removed ID: DCE:1b67ae18-914b-4fc7-bfe5-4d533693d44f
Waiting for events...
As directed, press CTRL+C
to kill the gevent Greenlet and end the Python session.
Wrapping up the Example
This example provided a single listener front-end for the ODMListener
. The IDMListener
can be accessed in a similar fashion from the ossie.utils.redhawk.channels
module. And with this simple example, its plain to be seen how easily one can access these two primary channels for a wide array of possible system tasks.
Use Cases
Throughout the previous sections, the ODM and IDM channels were defined, an example of events during the lifecycle of an entity, and an example class for monitoring those events within a Python environment. So where does one go from here? Both of these possible use cases assume the user has detailed knowledge of the system being deployed.
Case 1: Waveform Launching
Recall that REDHAWK provides a framework for locating system-level resources, automatically or by user command, when Waveforms are launched. REDHAWK also provides facilities for automatically tearing down and releasing those Waveforms if the system resources cease to exist. All these are fantastic features that can be implemented because the user directs part of the process.
So what if one needs automatic Waveform deployment?
Launching with Defaults
In this case, users can leverage the Python model to tap into the ODM and IDM Channels as shown in the Listener Code Example, and then launch Waveforms as required. Let’s first change the MyListener example to retain an instance of the Domain:
def __init__(self):
self.dom = redhawk.attach(redhawk.scan()[0])
self.dom._populateApps()
self.odm = ODMListener()
self.odm.connect(self.dom)
# The rest remains the same
Now we have access to the attached Domain and its internal list of available Waveform SADs has been updated 6. From the add
method one can search for a specifically named Device Manager in the sourceName
before attempting to launch the waveform:
def add(self, event):
rh_type = "{0}".format(event.sourceCategory)
if "DEVICE_MANAGER" == rh_type and
"some_name" == event.sourceName:
self.dom.createApplication( 'waveform_name' )
At this point, the application should be visible from the self.dom.apps
list. One can confirm the Application is started using the _get_started()
method.
Case 2: Load Balancing
Another use case for monitoring the ODM and IDM channels is to load balance deployment of Waveform Components. This task will require some knowledge of the system, but let us extend the previous example. The goal will be to launch a Waveform on a specific Device when its usage state indicates IDLE.
The default application launching method 7 does not ensure the newest instance of a Waveform’s Components will be deployed to particular Device(s). Through the Python model, the process is a bit more involved but still straight-forward.
First, the Core Framework structure DeviceAssignmentType
and an XML parser will be useful for connecting Component IDs to possible Device IDs.
from ossie.cf import CF
from xml.dom import minidom
Next, the previous MyListener
example needs to connect an IDMListener
to its Domain instance and bind a callback. For good cleanup, the __del__
method is appended with disconnect()
to cleanup the callback:
from ossie.utils.redhawk.channels import IDMListener
class MyListener():
def __init__(self):
# Same as before plus...
self.idm = IDMListener()
self.idm.connect(self.dom)
self.idm.usageStateChanged.addListener(
WeakBoundMethod(self.usageStateChanged))
def __del__(self):
# Same as before plus...
self.idm.disconnect()
NOTE: As seen previously, a Device might announce
IDLE
right before traversing fromUNLOCKED
toSHUTDOWN
andLOCKED
as it is removed from the Domain. One could listen toadministrativeStateChanged
to ensure the Device isUNLOCKED
andIDLE
, but this example is only monitoring the usage state.
The usageStateChanged
method bound previously needs to be implemented next:
def usageStateChanged(self, devId, fromState, toState):
toState = "{0}".format(toState)
if MY_DEV_ID == devId and "IDLE" == toState:
# Implementation discussed next
Then, rather than createApplication
, an Application Factory must be installed onto the Domain 8 for the Waveform. NOTE: the path shown here is relative to the Domain’s file manager root:
self.dom.installApplication( '/waveforms/my_wave/my_wave.sad.xml' )
af = self.dom._get_applicationFactories()[0]
Next, obtain a list of Component IDs from the SAD using the Domain’s file manager and the XML parser. The id
attribute is not a string, so compIDs
is provided here to show one method for creating an ID string list:
fm = self.dom._get_fileMgr()
sadx = fm.open(af._get_softwareProfile(), True)
buff = sadx.read(sadx.sizeOf())
sadx.close()
sad = minidom.parseString(buff)
compList = sad.getElementsByTagName( 'componentinstantiation' )
compIDs = [str(c.attributes['id'].value) for c in compList]
Finally, with some system knowledge knowledge of which Component IDs and Device IDs to bind 9, create a list Component IDs to Device IDs:
devReqs = [ CF.DeviceAssignmentType(compIDs[0], DEVICE_ID) ]
app = af.create(af._get_name(), [], devReqs)
app.start()
Important Closing Thoughts
First, the Device IDs being bound using the Application Factory method are expected to execute the Component and therefore must be both Executable Devices and support the requirements of at least one of the Component’s Implementations (CPU architecture, etc.).
Second, at this time, usesdevice
relationships cannot be forced in using this method. Instead, one should consider adding an allocation
Property to the Device as a reservation flag and configure the relationship to use this new flag. A more complex alternative for conglomerate Devices (i.e., those that manage several pieces of hardware) can be used to setup a port reservation system. See the USRP_UHD example and its customized port implementation for details.
Third, the devReqs
list can only supply a one-to-one relationship of Component IDs to Device IDs. It does not support adding multiple possible Device IDs for a single Component ID. For that behavior, a looped or recursive try-catch
when calling create()
may be good solutions.
Entity-level Channels
The REDHAWK Components and Devices are able to produce and consume events, internal to themselves, without much effort from the designer. For example a C++ implementation would simply add the following to bind a change to my_prop
to the class_i
object’s callback
method:
setPropertyChangeListener("my_prop", this, &class_i::callback);
For REDHAWK 2.0, getting property changes out of the Component or Device comes in two forms: listening for changes and using message
Properties (with MessageEvent
Ports). These are covered in detail below.
Common Details
Events and Messages share several similarities in both mechanics and implementations. At a high-level, both are:
- Light-weight and asynchronous transfers
- Implemented as Properties on an entity
- Result in a custom IDL (Interface Description Language)
- Carried via CORBA Remote Procedure Calls (RPC)
Both Events and Messages can be implemented with roughly the same steps:
- Add and define a Property with a unique ID
- Mark the Property’s
kind
as appropriate (property
ormessage
, respecitvely) - Generate All Implementations
Deeper nuances of these steps are covered in the next section. But first, let us unpack some of these high-level details.
Light-weight and Asynchronous
Despite being on Ports like BULKIO, neither Events or Messages are intended to convey large quantities (streams) of data which is not to suggest either is a slow transfer by any means. Rather, these types are intended to convey small bursts of control and status information asynchronously into the entity while its main service function might be churning through large chunks of BULKIO data.
Custom IDL
The user’s structure that defines the Property representing an Event or Message results in a new IDL definition. The uniqueness of one definition to another is driven not only by the structure but also the IDs.
If a deployed entity defines a property
or message
Property, the IDE’s Browse feature can be used to copy that IDL to a new entity. It is a nice IDE feature which glosses over a blink-and-you-missed-it and major detail.
NOTE: If one entity’s definition of a Property is altered, the change is not propagated to other entities which implement the previously-imported Property. Therefore the one entity’s Property is now not compatible with the previous definition.
The fix: all other definitions would need to be reimported or modified to match the new definition to restore communication. Naturally then, the early stabilizing of interfaces exposed as Events and Messages will pay dividends as one’s architecture scales larger.
Ports and RPCs
Passing Properties through the Event system architecture in REDHAWK is fairly simple despite the few manual steps. These steps also expose some of the deeper functionality related to CORBA on which these feature rely. Most importantly is understanding the design impact of the push and pull actions.
CORBA is itself a remote-procedure call (RPC) mechanism. When an object is pushed to a receiver, it’s actually a call on the receiver to pull the data to the receiver’s Port thereby making it available to the receiver’s entity 10.
The figure shows a Node’s two devices connected by set of message_*
Ports. The antenna_control_1
Device instance is the performing the push in this case; therefore it has an output MessageEvent
port for carrying its Message(s). The rtl_sdr_device_1
is consuming Message(s) therefore its port is bidirectional (as shown by both an input and output port called message_in
).
Why bidirectional?
Recall that the push is carried by an RPC-based mechanism. When the push occurs, the object is delivered to the pusher’s port for any receivers to pull, when available. This becomes a call for any attached receivers to perform a pull for data from the pusher’s Port. It is this pull that drives the receiver’s port needing to be bidirectional. The Port issues the pull call via its output and receives the data via its input.
Property Change Events
As shown previously, Properties have events 11 internal to the associated Device or Component that implements it. As of REDHAWK 2.0, Devices and Components are now PropertyEmitter
implementers — meaning any readable property
Property can be listened to remotely.
The following are some key points about Property Change Events:
- The Property
kind
must beproperty
and readable - When the Property changes by some external means 12, the change is not pushed immediately
- Changes can be conveyed point-to-point or broadcast into an Event Channel
The first point is straight-forward: you cannot listen for changes (periodically read) if the property is write-only.
Previously, the second point would be that changes get pushed automatically. However in REDHAWK 2.0, setting up the listener requires specifying the maximum-possible update interval in some decimal number of seconds. If changes occur during the interval, the changes will be pushed to the downstream listeners on the next update.
Finally the third point was introduced with REDHAWK 2.0 and has ensured a unified structure for property change events, regardless of how they’re conveyed.
struct PropertyChangeEvent {
string evt_id;
string reg_id;
string resource_id;
CF::Properties properties;
};
Manually Pushing Events
As for REDHAWK 2.0 this is no longer possible since the receiver defines the update interval. Instead, the sender only needs to maintain its properties as necessary and the receiver will get updates at its liesure.
Consuming Events with Entities
New to REDHAWK 2.0 is the ability to consume events from within a Device or Component. Doing so however does take some effort to setup the listener and the developer does have the responsibility to unregister from the upstream producer of the property change events when it is finished listening.
By some means, the implementer of the PropertyChangeListener
interface needs to get a reference to the producer. For example if a Component connected to a Device wanted to monitor specific properties on that Device, it could use the Connection Manager to determine the Device’s reference (dev
in this Python example below):
from ossie.cf.CF__POA import PropertyChangeListener
class MyListener(PropertyChangeListener):
def propertyChange(self, event):
print event
listener = MyListener()
listener_id = dev.ref.registerPropertyListener(listener._this(), ['property1_id'], 5.0)
# Some time later...
dev.ref.unregisterPropertyListener(listener_id)
In this example we’re registering to listen at an interval of 5 seconds to the Device’s Property whose ID is property1_id
by instantiating a PropertyChangeListener
and overloading the propertyChange()
method. Alternatively the PropertyChangeListener
could be added to the down-stream Component’s own base classes (depending on the language) and implement its own propertyChange()
method internally.
Another alternative is to replace listener._this()
with a reference to an event channel instance gained from the Event Channel Manager tied to the Domain. This would have the advantage of making the property changes be point-to-multipoint. One can get access to the Event Channel Manager from a Domain reference:
from ossie.utils import redhawk dom = redhawk.attach() ecm = dom.ref._get_eventChannelMgr()
See $OSSIEHOME/share/idl/ossie/CF/EventChannelManager.idl
for more information on its use for now. It’s a big subject and will be covered in a separate post soon.
Message Properties
REDHAWK also provides another specialized type of Property called a Message. It too is transferred using the Event subsystem making it also a light-weight, asynchronous transfer of uniquely-identified Properties between Devices and Components. However some of the underlying mechanics change making the implementation more user-specific.
First, adding a Message to a Device or Component requires that the associated Property be marked as a message
. Implicitly then, a message
Property will also not initially have a reference to an object to store its value(s).
Second, because message
Properties have no initial reference, a user’s implementation must be designed to take this into account. For the entity pushing the Property, the implementation must create the object. For a receiver, the entity must not expect a message to be present at all times (for example, the Property could be null
in C++);
Third, since a message
is a reference to an object, it must be represented as a Struct
type of Property. Once the user’s implementation is generated, the Property’s constructor will be created, often in a separate header 13.
Fourth, where Property Change Events were polled and pulled at intervals, Messages are manually pushed. The push still occurs on a Port, but the user must create at least one MessageEvent
Port (usually, bidirectional). Therefore unlike Property Change Events, Messages can be dedicated to individual Ports or propagated on multiple Ports.
Finally, unlike Property Change Events, Messages can much more easily be consumed by other Devices and Components or fed through Event Channels using FindBy
in the surrounding Node or Waveform (respectively).
Message Producer
Unlike Property Change Events, pushing a Message from an entity requires an object for the Property to be created and a MessageEvent
Port marked as an output. The Property is not pushed using its ID but rather its structure object which contains a field for the ID. This field will then be used by the consumer for mapping callbacks.
For example, a C++ implementation with a Message Property called pattern
with one long
field named value
would be:
struct pattern_struct {
pattern_struct() {
};
static std::string getId() {
return std::string("pattern");
};
CORBA::Long value;
};
Both Java and C++ follow this pattern for defining the structure (that is to say, adding _struct
to the ID). Python implementations will camel-case the ID for the name.
Continuing with this example, the C++ push on a port named message_out
would be the following:
pattern_struct msg; msg.value = 42; message_out->sendMessage(msg);
With these two simple requirements met, the entity is now able to push its Message to any consumers.
Message Consumer
Entities can consume messages by defining a MessageEvent
Port as bidirectional and defining the Property to exactly match the expected message 14. The entity must then register a callback for that Property’s ID on the associated Port.
For example, an entity with a C++ implementation which receives the previously defined pattern
Message would first need to declare the callback:
void receivedPattern(const std::string &id,
const pattern_struct &msg);
With the callback declared and defined for the entity, it can be registered after the entity is constructed (configure()
, for example). The callback must be registered with the MessageEvent
bidirectional Port(s) to use.
For example if the MessageEvent
Port is named message_in
on a C++ entity named my_consumer
, registering the callback would be:
message_in->registerMessage("pattern", this,
&my_consumer_i::receivedPattern);
A Python-based consumer follows a similar pattern. The primary difference in registering the callback involves passing a reference to the expected structure rather than a reference to the entity. This allows the incoming message to be converted from the CORBA ANY into the anticipated Message structure:
self.port_message_in.registerMessage("pattern",
my_consumer_base.Pattern,
self.receivedPattern)
However, the associated callback still only receives a Message’s Property ID and structure:
def receivedPattern(self, msgID, msgData):
pass
An equivalent Java implementation is considerably more involved, and its example is not repeated here. The example can be found in the Events section of the main REDHAWK SDR documentation.
Bridging Interactions
At this point one might be wondering how to interconnect all of these concepts without directly having to explicitly define the connection between the Ports within a Node or Waveform. Three possible solutions are covered: usesdevice
, automated connections via Python, and using FindBy
to create public pools of traffic.
In any case, recall the whole mechanism for conveying Events and Messages is a wrapper for CORBA — an RPC mechanism. Therefore the most basic concept can be summarized as the User Connects to the Provider.
The UsesDevice Relationship
Often Components and Devices need to communicate directly by connecting Ports. If enough is known about the target Device to identify it uniquely in the system, and the Port types match, a usesdevice
relationship can be added to the Waveform supplying the Component. However since Property Change Events are ingested in more of an administrative, higher-level contexts, this section only applies to MessageEvent
Ports.
NOTE: At this time, breaking a
usesdevice
relationship is not grounds for stopping or releasing a Waveform or its Components. This behavior can be accomplished by monitoring the main system channels and responding accordingly when the ports have implicitly been disconnected 15.
Establishing the connection in the Waveform requires changes to the XML unless you are connecting to a Frontend Interface (FEI) Device, in which case there is a wizard in the Waveform Advanced pallet. Because it’s a bit more complex, this example will be a manual XML edit with a Component receiving Messages from a Device.
First, describe the connection in the connections
list:
message_out message_in
The usesidentifier
and providesidentifier
tags define the names of the usesport
and providesport
Ports to connect. The next element identifies the target entity as being either a Component (in the Waveform) or a Device to locate. If the target is a device, a usesrefid
is provided for the next step.
Next, the usesdevicedependencies
section should be added after connections
if it does not already exist. Within it, describe how to locate the Device by defining a usesdevice
with the id matching the usesrefid
in the previous step:
Finally, populate the usesdevice
with all of the property-value pairs to configure or allocate that would adequately describe the required Device. These Properties must be property
or allocation
and will result in the attempted configuration or allocation using provided values, providing a clean way to add a reservation-style mechanism to your architecture.
For example, if the target had a boolean allocation
Property with ID reserveable
. The resulting description would then be:
If the Device has not already allocated this simple Property, the connection will be established. When the Waveform is released, the Device will be issued a deallocate()
call to release its internal reservation state.
FEI Wizard
As of REDHAWK 2.0, the Waveform editor’s tool palette now has Use FrontEnd Tuner Device
which can be dropped into the diagram and connected to the requisite ports. This begins a wizard that helps setup the allocation properties and values you chose for either a generic FEI device or specific to one known to file system (i.e., deployed to the Target SDR
). We’re skipping over a detailed introduction to it because it simplifies the above process of manually editing XML files into something as simple as filling out a form and drawing a line between ports. It’s that good.
Connections in Python
The Python interface to the REDHAWK model provides several interesting advantages to explicitly defining connections — especially when the connection is desired to be Node-Node, Waveform-Waveform, or Node-Waveform. Coupled with the Listener Example, a much more powerful relationship can be maintained than given by the usesdevice
mechanism.
The first step for locating Devices and Components is to connect to the named domain (import
included for completeness):
from ossie.utils import redhawk
domain = redhawk.attach(<domain name>)
How one locates the Devices and Components will require some decision making. At the top-level, the Domain model provides direct access to all Devices in the Domain via its devices
list. Components have no equivalent list in the Domain.
If the system hierarchy is known, it might be better to peruse the hierarchy for the specific entity pair in question. The starting point then are the Domain’s devMgr
and apps
lists. For example, the third Component in the second running Application (zero-indexed) would be:
my_comp = domain.apps[1].comps[2]
Once the two entities are found, the connection can be established if it is unambiguous (i.e., only 1 port could possibly match between source and destination). Otherwise the providesPortName
and usesPortName
can be defined to clarify the connection, for example:
my_uses.connect(my_provider,
usesPortName='my_uses_port',
providesPortNmae='my_providers_port');
If necessary, a connectionId
can also be added as an argument to connect
. Later if the connection needs to break, simply call disconnect(providerInstance)
on the user:
my_uses.disconnect(my_provider)
Now consider the impact of coupling this interaction with the previous Listener Example, or a more generic Event Listener subclassing CosEventComm__POA.PushConsumer
. The result is a powerful mechanism for automating the creation of direct (private) event channels between entities.
Using FindBy
The Event Channel
element can be added to a Node or Waveform using the associated editor’s Find By
palette and has several features including locating file systems, Domains, etc. It also provides the ability to not only connect to the ODM and IDM channels but also declare new channels. These new channels can also be monitored using eventViewer
or ingested by subclassing CosEventComm__POA.PushConsumer
making this feature a powerful mechanism for pooling common Property Change Events and Messages into a Domain-wide (public) channel.
NOTE: If multiple producers are pushing Messages into the same named channel, the consumer(s) will be unable to determine which producer emitted the Message unless the structure contains a unique identifier. A simple solution is to add a field to the Property structure to carry the entity’s
identifier()
(C++).
First, for the entity producing Messages or Events to be pushed into a channel, the appropriate FindBy
must be added to whatever is enclosing it (Node or Waveform). In this case, we are interested in the Event Channel
.
By double-clicking or dragging the element to the diagram, a pop-up window will give you the opportunity to specify the named Event Channel of interest.
Finally, connect the output (uses
) Port to the FindBy
similar to how you would to a Port except you should point to the antenna-like element.
The example in the figure propagates Messages from the MessageEvent_out
Port into a new channel, the_new_channel
. If some other entity or observer needs access to the information, it simply joins the_new_channel
to monitor the messages and/or ingest them using a MessageEvent
Port mapped internally to an identically-defined message
Property on the receiver.
Note: A consumer configuration is implemented similarly. Rather than connecting a
uses
Port to theFindBy
, theFindBy
would be connected to aprovides
Port.
Conclusion
Throughout the preceding sections several powerful topics have been explored related to Event Channels and the traffic within them. The primary Domain channels and associated events were examined and a code example showed how to leverage these events for system administration. Then Entity-level Property Change Events and Messages were explored along with several options for interconnecting entities with these low-latency asynchronous events. Using these concepts, dynamic system design and administration can be achieved with relatively little effort.
- By the SCA Specification ↩
-
Requirements either apply one of a Component’s implementations to a Device, or via
usesdevice
connect two Ports together. ↩ -
The
usesdevice
requirements in a Waveform are not grounds for automatically stopping and releasing a Waveform at this time. ↩ -
Line:
redhawk.attach(...)
↩ -
Line:
redhawk.scan()[0]
↩ -
Line:
self.dom._populateApps()
↩ - Case 1: Waveform Deployment ↩
- Note: If other Application Factories are installed, you will need to search the list for the name. ↩
-
A Device list is available on the domain,
self.dom.devices
in this case. ↩ - In this case, entity is vaguely used since the receiver could be a Device or Component. ↩
- Also known as @notification decorations in Python. ↩
-
An example of an external change is
configure()
being called on the entity. ↩ -
For example with C++ implementations, the
struct_props.h
file is created for all defined structs. ↩ - See Common Details section regarding IDL definitions. ↩
- See the Domain-Level Channels section and the Python-based listener example. ↩