Events and Channels

 

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 was weakmethod (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 from UNLOCKED to SHUTDOWN and LOCKED as it is removed from the Domain. One could listen to administrativeStateChanged to ensure the Device is UNLOCKED and IDLE, 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:

  1. Light-weight and asynchronous transfers
  2. Implemented as Properties on an entity
  3. Result in a custom IDL (Interface Description Language)
  4. Carried via CORBA Remote Procedure Calls (RPC)

Both Events and Messages can be implemented with roughly the same steps:

  1. Add and define a Property with a unique ID
  2. Mark the Property’s kind as appropriate (property or message, respecitvely)
  3. 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.

Messaging as implemented in a Node

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:

  1. The Property kind must be property and readable
  2. When the Property changes by some external means 12, the change is not pushed immediately
  3. 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.

FindBy implemented to create the_new_channel and siphon off Messages from message_out.

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 the FindBy, the FindBy would be connected to a provides 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.


  1. By the SCA Specification 
  2. Requirements either apply one of a Component’s implementations to a Device, or via usesdevice connect two Ports together. 
  3. The usesdevice requirements in a Waveform are not grounds for automatically stopping and releasing a Waveform at this time. 
  4. Line: redhawk.attach(...) 
  5. Line: redhawk.scan()[0] 
  6. Line: self.dom._populateApps() 
  7. Case 1: Waveform Deployment 
  8. Note: If other Application Factories are installed, you will need to search the list for the name. 
  9. A Device list is available on the domain, self.dom.devices in this case. 
  10. In this case, entity is vaguely used since the receiver could be a Device or Component. 
  11. Also known as @notification decorations in Python. 
  12. An example of an external change is configure() being called on the entity. 
  13. For example with C++ implementations, the struct_props.h file is created for all defined structs. 
  14. See Common Details section regarding IDL definitions. 
  15. See the Domain-Level Channels section and the Python-based listener example. 

Recent Posts

Ready for an exciting change?

Work with US!