Implementing Custom Interfaces¶
30 Apr 2015
This chapter outlines how the PLEXIL Executive can be interfaced to arbitrary external systems through the addition of custom interface adapters and exec listeners.
Overview¶
Interface adapters allow plans to query state, perform required actions, and provide feedback to the outside world. Exec listeners allow external observers to monitor changes in plan state.
Implementing Interface Adapters¶
Interface adapters handle three types of operations: lookups, commands, and planner updates. Each is (usually) independent of the others, though this is a design choice left to the interface implementor. In addition, they are required to implement the lifecycle API below to manage initialization, activation, deactivation, and shutdown.
The abstract base class InterfaceAdapter declares the API which interface developers must implement.
Lifecycle¶
Please see The Lifecycle Model for an overview of the phases of application execution.
Interface adapter classes must implement the following lifecycle management methods, whose prototypes are declared in the InterfaceAdapter abstract base class:
bool initialize(); // set up internal data structures
bool start(); // start communicating
bool stop(); // stop communicating
bool reset(); // reset to initialized state
bool shutdown(); // release any resources and prepare to be deleted
The return value for each method is a bool
value representing
whether the method succeeded or failed. Success is indicated by a
true
return value. So a minimal initialize()
method would look
like this example:
bool MyAdapter::initialize()
{
return true;
}
Data Flow and Threading¶
In the PLEXIL Executive, there is a thread dedicated to running the execution engine. Adapter methods called by the exec are called from this thread.
Results of execution (LookupOnChange data, command and update acknowledgments, command return values) are often (but not always) delivered by other threads. These results are stored temporarily in a queue inside the interface manager, and delivered to the execution engine when its thread is active again.
The exec thread sleeps between execution cycles. Wakeups are delivered
by calling InterfaceAdapter::notifyOfExternalEvent()
. Enqueued data
are processed immediately after the wakeup is delivered.
Application developers can choose between waking the exec after every datum is delivered, waking up after a batch of data is delivered (e.g. enqueuing a telemetry packet’s worth of data at a time), or running the exec thread on a strict tick-based schedule. The choice is a tradeoff between responsiveness requirements, CPU utilization, scheduler overhead, and other application dependent factors.
Lookups¶
The PLEXIL language defines two external state query operators, LookupNow and LookupOnChange. LookupNow largely means what it says: “give me the current value of this external state.” LookupOnChange means “give me the current value, AND tell me when the value changes.”
Lookups in the PLEXIL exec are implemented with the aid of a state cache, a table of state names and their most recent values with timestamps. Putting a value in the state cache when there is no active lookup for that state name is permitted.
Adapter Methods¶
The following member function is called for both LookupNow and LookupOnChange:
void lookupNow(State const &state, StateCacheEntry &cacheEntry);
lookupNow()
returns the requested value by updating the
StateCacheEntry
object supplied in the call.
Important
This member function is called synchronously inside
the PLEXIL exec’s main decision-making loop. Blocking (e.g. waiting for
another process, or another system, to respond) during a lookupNow()
call should be avoided whenever possible, as it delays plan execution.
LookupOnChange can be implemented by the following member functions:
void subscribe(const State& state);
Tells the interface adapter to notify the exec whenever a new value for the state arrives.
void unsubscribe(const State& state);
Tells the interface adapter to stop notifying the exec of new values for this state.
LookupOnChange
of rapidly varying numeric quantities can be optimized
with the following optional member functions:
void setThresholds(const State& state, double hi, double lo);
void setThresholds(const State& state, int32_t hi, int32_t lo);
These tell the adapter to ignore changes in the state’s value until they meet or exceed the supplied thresholds.
Note
Any adapter for the time
state MUST implement
setThresholds()
.
StateCacheEntry member functions¶
The StateCacheEntry class provides the following member functions for implementing return values from LookupNow:
void setUnknown();
Returns an UNKNOWN value.
void update(bool const &val);
void update(int32_t const &val);
void update(double const &val);
void update(std::string const &val);
These return a Boolean, Integer, Real, or String value respectively.
void updatePtr(std::string const *valPtr);
This returns a String value.
void updatePtr(BooleanArray const *valPtr);
void updatePtr(IntegerArray const *valPtr);
void updatePtr(RealArray const *valPtr);
void updatePtr(StringArray const *valPtr);
These are used to return array values.
void update(Value const &val);
This is the generic version for returning a Value instance.
AdapterExecInterface member functions¶
These member functions are used to return values from LookupOnChange, or to post values to the state cache asynchronously, independent of any queries.
void handleValueChange(State const &state, const Value& value);
Enqueues a state value update entry in the interface manager’s queue.
void notifyOfExternalEvent();
Tells the interface manager to wake up the exec and process the entries
in the queue. Several handleValueChange()
and other calls can be
batched and processed by a single notifyOfExternalEvent()
call.
Commands¶
Adapter methods¶
Adapter classes must implement the following methods to support commands.
void executeCommand(Command *cmd);
Send the command to the external system. At the very least this method must cause a command handle value to be sent back to the exec.
void invokeAbort(Command *cmd);
Abort the specified command. Called when a Command node is prematurely ended. This method must at minimum cause an abort acknowledgment to be sent back.
Command member functions¶
These are functions for extracting information from a Command object.
std::string const &getName() const;
Get the command’s name as a string.
std::vector<Value> const &getArgValues() const;
Get a vector of the argument values.
AdapterExecInterface member functions¶
These are the interface manager member functions for responding to a command.
void handleCommandAck(Command * cmd, CommandHandleValue value);
Enqueues the command handle (acknowledgment) value in the interface manager’s queue.
void handleCommandReturn(Command * cmd, Value const& value);
Enqueues the command’s return value in the interface manager’s queue.
void handleCommandAbortAck(Command * cmd, bool ack);
Enqueues the abort acknowledgment in the interface manager’s queue.
void notifyOfExternalEvent();
As with lookups, tells the interface manager to wake up the exec and process the queue. Also as with lookups, several enqueued responses can be processed with a single call.
Update Nodes¶
Comprehensive documentation on interfacing the Update Node (or planner
update) is yet to be supplied. The following is a quick guide. Let’s
assume your interface adapter class, derived from InterfaceAdapter
is named MyAdapter
.
1. Don’t use the virtual function
InterfaceAdapter::sendPlannerUpdate(Update*)
, which is deprecated.
Instead, define a static member function in MyAdapter
that has the
following signature, using any function/argument names you like:
static void myPlannerUpdate (PLEXIL::Update* update, PLEXIL::AdapterExecInterface* exec);
2. This function, the Update node handler, must contain the following line in order to transition the Update node to the Finished state. Your application logic will determine where to place this line.
exec->handleUpdateAck (update, true);
3. In MyAdapter::initialize()
, the following line is needed to
register your Update handler:
g_configuration->registerPlannerUpdateHandler(OwAdapter::myPlannerUpdate);
Note that g_configuration
is a global variable defined in the PLEXIL
header AdapterConfiguration.hh
, which you may need to include.
4. In your interface configuration (.xml
file), in the <Adapter AdapterType="...">
element, add
the following (empty) element:
<PlannerUpdate/>
Registration¶
Interface adapters must be registered with the AdapterConfiguration object. There are several ways to handle this.
more to be supplied
Implementing Exec Listeners and Filters¶
‘’to be supplied’