Skip to main content
U.S. flag

An official website of the United States government

Official websites use .gov
A .gov website belongs to an official government organization in the United States.

Secure .gov websites use HTTPS
A lock ( ) or https:// means you’ve safely connected to the .gov website. Share sensitive information only on official, secure websites.

NML Module

NML_Module

Introduction

NML_MODULE is a base class for RCS control modules that primarily use NML for communications. This has sometimes been referred to as the RCS Template, although it is not defined with the C++ keyword template. It is used by deriving an application specific class from NML_MODULE and overloading some of the functions. The NML_MODULE provides each module with a consistent structure and performs some of the common chores such as measuring performance, initializing NML and checking for new commands. Many of the tasks for setting up a module can also be performed automatically with the RCS Design Tool.

Defining a New Class for a Control Module

In order for the main program to create an instance of this module, it needs to have a definition of the class in a C++ header file.  The following provides an example: /* mcps.hpp  This C++ header file defines the class MCPS_MODULE It was generated with the RCS-Design tool.  MODIFICATIONS: 1-Dec-97Modified by RCS-Design tool. 21-Oct-97Created by RCS-Design tool.  */  // Prevent Multiple Inclusion #ifndef MCPS_HPP #define MCPS_HPP  // Include Files #include "rcs.hh" // Common RCS definitions #include "nml_mod.hh" // NML_MODULE definitions #include "mcpsn.hpp" // NML Commands and Status definitions for mcps #include "mcexmon.hpp" // NML Commands and Status definitions for mcexmo #include "mcexton.hpp" // NML Commands and Status definitions for mcexto  // Auxilliary Input NML Message Files #include "mcps_auxinn.hpp"// NML Messages for mcps_auxin  // Auxilliary Output NML Message Files #include "mcps_auxoutn.hpp"// NML Messages for mcps_auxout  class MCPS_MODULE: public NML_MODULE { // RCS-Design-MERGE-DISABLE Edits to the following area will NOT be preserved by the RCS-Design tool.  public:  MCPS_MODULE(); // Constructor  // Overloaded Virtual Functions void PRE_PROCESS(); void DECISION_PROCESS(); void POST_PROCESS();  // Command Functions void INIT(MCPS_INIT *); void HALT(MCPS_HALT *); void HOME(MCPS_HOME *); void MOVE(MCPS_MOVE *); void CUT(MCPS_CUT *);  // Convenience Variables MCPS_STATUS mcps_status; int mcexmo_sub_num; MCEXMO_STATUS *mcexmo_status; int mcexto_sub_num; MCEXTO_STATUS *mcexto_status;  // Auxiliary Input NML Channels NML *MCPS_AUXIN_CHANNEL;// NML Channel for mcps_auxin MCPS_AUXIN_MSG *mcps_auxin_data;// NML Data for mcps_auxin  // Auxiliary Output NML Channels NML *MCPS_AUXOUT_CHANNEL;// NML Channel for mcps_auxout MCPS_AUXOUT_MSG mcps_auxout_data;//NML Data for mcps_auxout // RCS-Design-MERGE-ENABLE Edits after this line will be preserved by the RCS-Design tool.   private: // Add custom variables and functions here.  };  #endif // MCPS_HPP

The header needs to include seven other C++ header files. "rcs.hh" contains definitions for most of the utilities in the RCS Library. " nml_mod.hh" contains the base class NML_MODULE. "mcpsn.hpp" contains definitions for the NML interface to this module, the classes that can be sent as commands such as MCPS_INIT, MCPS_HALT, MCPS_HOME, MCPS_CUT, and MCPS_MOVE and the class used to provide status to its superior MCPS_STATUS. "mcexmon.hpp" and "mcexton.hpp" provide the NML messages for the two subordinates mcexmo and mcexto. "mcps_auxinn.hpp" provides the message MCPS_AUXIN_MSG read from the auxiliary input channel. "mcps_auxoutn.hpp" provides the message MCPS_AUXOUT_MSG sent to the auxiliary output channel.

The functions PRE_PROCESS, DECISION_PROCESS, and POST_PROCESS are defined in the NML_MODULE base class but typically overloaded in each control module. These functions are called every cycle by the NML_MODULE::controller function which is not normally overloaded. (RCS uses cyclic processing, normally with a constant cycle time such as 30 ms set as a parameter to the RCS_TIMER constructor in the main loop.)

PRE_PROCESS is normally used for retrieving inputs and performing simple conversions needed every cycle and by multiple commands. For example, one might read an encoder input and multiply by a scale factor to convert encoder ticks to mm. For more complicated sensor processing, it may be better to develop a separate module just for sensory processing or even an entire hierarchy. It can also be used to read any auxiliary input.

DECISION_PROCESS usually calls one of the command functions based on the current command type. The command functions for this module are INIT, HALT, MOVE, CUT and HOME.

POST_PROCESS is normally used for writing outputs and performing simple conversions on the outputs needed every cycle by multiple commands. For example, one might multiply a commanded velocity by a scale factor before writing a voltage to a DAC (Digital-to-Analog Converter). It can also be used to write any auxiliary output.

The command functions usually contain state tables made up of multiple "if ... else if ... " blocks. The state tables are designed so that only a small amount of processing needs to occur each cycle and long complex commands are spread over many cycles. The current line in the state table is the first line where the conditions for the if are true. This line is highlighted within the RCS Diagnostics tool.

The variable mcps_status will be passed as a parameter to NML_MODULE::setStatChannel inside the constructor. Then it will be sent to the NML channel every cycle. Information can be sent to the superior just by defining the appropriate variable in MCPS_STATUS and setting it in one of the command functions, PRE_PROCESS or POST_PROCESS.

The variable mcexmo_sub_num recieves the subordinate number returned by NML_MODULE::addSubordinate() that is needed by the function sendCommand() to send commands to the mcexmo module. The variable mcexmo_status is set to point to the buffer where status data from the mcexmo module will be updated every cycle.

The comments "// RCS-Design-MERGE-DISABLE" and "// RCS-Design-MERGE-ENABLE" separate out an area of the file users of the RCS-Design tool may wish to avoid editing, since those edits will be deleted if the file is modified with the RCS Design tool.

The following are segments from the actual code for the MCPS control module. (Click here to see the complete file, "mcps.cc".)

// Constructor MCPS_MODULE::MCPS_MODULE() {  // Set up NML Channels // RCS-Design-MERGE-DISABLE Edits to the following area will NOT be preserved by the RCS-Design tool.  setErrorLogChannel(new NML(nmlErrorFormat, "errlog", "mcps", "isam.nml")); setCmdChannel(new RCS_CMD_CHANNEL(mcpsFormat, "mcps_cmd", "mcps", "isam.nml")); setStatChannel(new RCS_STAT_CHANNEL(mcpsFormat, "mcps_sts", "mcps", "isam.nml"), &mcps_status);  mcexmo_sub_num =  addSubordinate( new RCS_CMD_CHANNEL(mcexmoFormat, "mcexmo_cmd", "mcps", "isam.nml"), new  RCS_STAT_CHANNEL(mcexmoFormat, "mcexmo_sts", "mcps", "isam.nml")); mcexmo_status = (MCEXMO_STATUS *)  statusInData[mcexmo_sub_num];  mcexto_sub_num =  addSubordinate( new RCS_CMD_CHANNEL(mcextoFormat, "mcexto_cmd", "mcps", "isam.nml"), new  RCS_STAT_CHANNEL(mcextoFormat, "mcexto_sts", "mcps", "isam.nml")); mcexto_status = (MCEXTO_STATUS *)  statusInData[mcexto_sub_num];  // Auxiliary Input NML Channels //mcps_auxin MCPS_AUXIN_CHANNEL = new NML(mcps_auxinFormat, "mcps_auxin", "mcps", "isam.nml"); mcps_auxin_data = (MCPS_AUXIN_MSG *) MCPS_AUXIN_CHANNEL->get_address();  // Auxilliary Output NML Channels //mcps_auxout MCPS_AUXOUT_CHANNEL = new NML(mcps_auxoutFormat, "mcps_auxout", "mcps", "isam.nml"); // RCS-Design-MERGE-ENABLE Edits after this line will be preserved by the RCS-Design tool.  // Add additional code to initialize the module here.  }

The constructor is used to setup the NML channels for the module as well as any other initialization that needs to be done. The command, status and error log channels for this subordinate are setup with the functions NML_MODULE::setCmdChannel(), NML_MODULE::setStatChannel(), and NML_MODULE::setErrorLogChannel. The connections to each subordinate are setup with NML_MODULE::addSubordinate(). Finally, any auxiliary inputs or outputs are setup.

/* DECISION_PROCESS  The DECISION_PROCESS function is called every cycle as long as there is a non-zero command. It is expected to call a command function based on commandInData->type.  */ void MCPS_MODULE::DECISION_PROCESS() { switch(commandInData->type) { // RCS-Design-MERGE-DISABLE Edits to the following area will NOT be preserved by the RCS-Design tool.  case MCPS_INIT_TYPE: INIT((MCPS_INIT *)commandInData); break;  case MCPS_HALT_TYPE: HALT((MCPS_HALT *)commandInData); break;  case MCPS_HOME_TYPE: HOME((MCPS_HOME *)commandInData); break;  case MCPS_MOVE_TYPE: MOVE((MCPS_MOVE *)commandInData); break;  case MCPS_CUT_TYPE: CUT((MCPS_CUT *)commandInData); break; // RCS-Design-MERGE-ENABLE Edits after this line will be preserved by the RCS-Design tool.   default: logError("The command %d is not recognized.",commandInData->type); break; } }

The DECISION_PROCESS function calls the appropriate command function based on the current command type and passes commandInData to that function.

/* INIT  Parameter(s): MCPS_INIT *cmd_in -- NML Message sent from superior.  Most Modules will have an INIT command. The INIT function is expected to initialize any variables that may be in an uninitialized or unknown state, send INIT commands to its subordinates, wait for the subordinates to be DONE and then inform its superior that it is done The state tables should use the STATE_MATCH macro so the diagnostics tool can  highlight the current line in the state table.  */ void MCPS_MODULE::INIT(MCPS_INIT *cmd_in) { // RCS-Design-MERGE-DISABLE Edits to the following area will NOT be preserved by the RCS-Design tool.  MCEXMO_INIT mcexmoInitMsg; MCEXTO_INIT mcextoInitMsg; // RCS-Design-MERGE-ENABLE Edits after this line will be preserved by the RCS-Design tool.   if(STATE_MATCH(NEW_COMMAND)) { // Send an INIT command to all subordinates. // RCS-Design-MERGE-DISABLE Edits to the following area will NOT be preserved by the RCS-Design tool.  sendCommand(&mcexmoInitMsg, mcexmo_sub_num); sendCommand(&mcextoInitMsg, mcexto_sub_num); // RCS-Design-MERGE-ENABLE Edits after this line will be preserved by the RCS-Design tool.   stateNext(S1); // Reinitialize variables here.  } // Wait for all subordinates to report done. // RCS-Design-MERGE-DISABLE Edits to the following area will NOT be preserved by the RCS-Design tool.  else if(STATE_MATCH(S1, mcexmo_status->status == RCS_DONE && mcexto_status->status == RCS_DONE && 1)) // RCS-Design-MERGE-ENABLE Edits after this line will be preserved by the RCS-Design tool.  { status = RCS_DONE; stateNext(S2); } else if(STATE_MATCH(S2)) { // Idle State } }  The INIT function provides a good example for a command function.  When a new command is received, the state is set to NEW_COMMAND.  On the first cycle, the state table sends commands to its subordinates, mcexmo and mcexto, with the sendCommand function, sets the state  to S1 and returns. The next cycle the state table checks to if the status of both mcexmo and mcexto is RCS_DONE. If so the state table  sets its own status to  RCS_DONE and state to S2.

Writing the Main Loop(s)

In order to run the module it is necessary to call its controller() function from the main function for the program. In addition, the programmer has the option of combining several modules in the same executable with eliminates some of the task switching and mutual exclusion overhead and allows for tighter synchronization. It is a good idea to keep the code in this file to a minimum so that the application can be reorganized easily.

The following is an example:

/* isammain.cc  This file provides the C++ main function which creates and runs the following control modules:  MOTION_MODULE MCWM_MODULE MCEXTO_MODULE MCVJ_MODULE MCSP_MODULE MCPS_MODULE MCPL_MODULE MCEXMO_MODULE TOOL_MODULE  MODIFICATIONS: 1-Dec-97Created.  */  // Include Files #include // exit() #include // SIGINT, signal() #include "rcs.hh" // Common RCS definitions #include "nml_mod.hh" // NML_MODULE definitions #include "motion.hpp"// definition of MOTION_MODULE #include "mcwm.hpp"// definition of MCWM_MODULE #include "mcexto.hpp"// definition of MCEXTO_MODULE #include "mcvj.hpp"// definition of MCVJ_MODULE #include "mcsp.hpp"// definition of MCSP_MODULE #include "mcps.hpp"// definition of MCPS_MODULE #include "mcpl.hpp"// definition of MCPL_MODULE #include "mcexmo.hpp"// definition of MCEXMO_MODULE #include "tool.hpp"// definition of TOOL_MODULE  // flag signifying main loop is to terminate int isam_done = 0;  //signal handler for ^C extern "C" void isam_quit(int sig); void isam_quit(int sig) { isam_done = 1; }  // main loop, running 9 controller(s) int main(int argc, char **argv) {  set_rcs_print_destination(RCS_PRINT_TO_STDOUT);  RCS_TIMER *timer = new RCS_TIMER(0.1); MOTION_MODULE *motion = new MOTION_MODULE(); MCWM_MODULE *mcwm = new MCWM_MODULE(); MCEXTO_MODULE *mcexto = new MCEXTO_MODULE(); MCVJ_MODULE *mcvj = new MCVJ_MODULE(); MCSP_MODULE *mcsp = new MCSP_MODULE(); MCPS_MODULE *mcps = new MCPS_MODULE(); MCPL_MODULE *mcpl = new MCPL_MODULE(); MCEXMO_MODULE *mcexmo = new MCEXMO_MODULE(); TOOL_MODULE *tool = new TOOL_MODULE();  // set the SIGINT handler signal(SIGINT, isam_quit);  // enter main loop while(!isam_done) { motion->controller(); mcwm->controller(); mcexto->controller(); mcvj->controller(); mcsp->controller(); mcps->controller(); mcpl->controller(); mcexmo->controller(); tool->controller();  timer->wait(); }  // Delete Modules delete motion; delete mcwm; delete mcexto; delete mcvj; delete mcsp; delete mcps; delete mcpl; delete mcexmo; delete tool;  // Delete Timer delete timer; }
The file simply includes the header file containing the definition of each of the control module classes and creates one object from each class. Inside a while loop the controller function is called for each object followed by a function that puts the process to sleep until time for the next cycle. The RCS_TIMER::wait function is specifically designed for this and sleeps a period that varies depending on the amount of processing the modules do so that the period between cycles remains consistent. For example, if for one cycle the sum of the time required to run all of the controllers is 40 ms and the timer was initialized at 0.1s = 100ms. The wait function will put the process to sleep 60 ms so the total time for the cycle is 100 ms. (See RCS Library Lower Level Utilities.) It is necessary to have some way of breaking the loop to shutdown the controller gracefully, in this example I have setup a signal handler for " C" to do this.
 

List of NML_MODULE Member Functions and Variables

The functions and variables for NML_MODULE are listed here in alphabetical order.

int addSubordinate(RCS_CMD_CHANNEL *, RCS_STAT_CHANNEL *);
The addSubordinate function is normally called from within the module's constructor to set an NML channel to send commands to a particular subordinate and another NML channel to check for status updates from that subordinate every cycle. It returns the subordinate's number used to identify it in calls to sendCommand and as an index into the statusInData array.
int checkDclock();
Returns 1 if the count down delay timer has expired, 0 otherwise.
RCS_CMD_MSG *commandInData;
The variable commandInData points to an area of memory where commands are placed when they are received
void controller();
The controller function is called every cycle from the main loop. It reads the NML input buffers, calls the functions PRE_PROCESS, DECISION_PROCESS, and POST_PROCESS, writes to the NML output buffers, and updates the performance metrics.
virtual void DECISION_PROCESS();
The DECISION_PROCESS function is called by the controller function. Its purpose is to call one of the command functions based on the current command type.
int loadDclock(double seconds);
Initializes a count down delay clock with will expire causing checkDclock to return 1 after the number of seconds specified. Fractions of a second are allowed. The actual resolution depends on the accuracy of the system clock.
void logError(const char *fmt, ...);
The logError function logs a message to the NML channel specified with setErrorLogChannel. The message will contain a string created from the format and variable number of arguments using the printf conventions. The message can be retrieved and displayed by the RCS Diagnostics tool or a custom GUI. (Graphical User Interface)
void logText(const char *fmt, ...);
The logText function logs a message to the NML channel specified with setErrorLogChannel. The message will contain a string created from the format and variable number of arguments using the printf conventions. The message can be retrieved and displayed by the RCS Diagnostics tool or a custom GUI. (Graphical User Interface)
void modifyCommand(RCS_CMD_MSG *, int subordinate_number);
Sends the command specified by the first argument to the subordinate specified by the second in the same way as sendCommand except that the serial number is not incremented. The subordinate will not see this as a new command but rather continue processing the old command but with the new parameters.
virtual void POST_PROCESS();
The POST_PROCESS function is called by the controller function after DECISION_PROCESS. Its purpose is to perform simple conversions and output processing and write any auxiliary outputs that need to be done every cycle regardless of the current command or state.
virtual void PRE_PROCESS();
The PRE_PROCESS function is called by the controller function before DECISION_PROCESS. Its purpose is to perform simple conversions and sensory processing and read any auxiliary inputs that need to be done every cycle regardless of the current command or state.
void requestDisplay(const char *url);
The requestDisplay function logs a message to the NML channel specified with setErrorLogChannel. The message will contain a string with the URL. (Universal Resource Locator). The RCS Diagnostics tool or a custom GUI (Graphical User Interface) can have a web browser automatically open to the URL, where extended pre-written instructions for the operator would be located.
void sendCommand(RCS_CMD_MSG *, int subordinate_number);
Sends the command specified by the first argument to the subordinate specified by the second.
void setCmdChannel(RCS_CMD_CHANNEL *);
The setCmdChannel function is normally called from within the module's constructor to set an NML channel to check for commands every cycle.
void setErrorLogChannel(NML *);
The setErrorLogChannel function is normally called from within the module's constructor to set an NML channel where error messages will be written when logError is called.
void setStatChannel(RCS_STAT_CHANNEL *, RCS_STAT_MSG *);
The setStatChannel function is normally called from within the module's constructor to set an NML channel and the address of a message to be written out every cycle with status information for the module's superior.
int STATE_MATCH(int state, int conds);
The STATE_MATCH function is used within the state tables and allows the current line in the state table to be tracked in the diagnostics tool. It returns a non-zero integer if the current state is equal to the first argument and the second argument evaluates to a non-zero number, otherwise it returns zero.
int status;
The current status of this module. This value is always sent to the supervisor. It should normally be equal to one of the following symbolic constants: RCS_EXEC,RCS_DONE, or RCS_ERROR. The rest of the data sent to the superior can be accessed at RCS_STAT_MSG *statusOutData, but users of the RCS-Design tool will ussually find it more convenient to use the variable that is automatically created of the form _status. This variable points to the same location but has a module-specific type.
RCS_STAT_MSG **statusInData;
The statusInData variable points to an array of buffers where the statuses of subordinates are placed when they are received. Users of the RCS-Design tool will ussually find it more convenient to use the variable that is automatically created of the form _status. This variable points to the same location but has a module-specific type.
RCS_STAT_MSG *statusOutData;
Pointer to the buffer of data that will be sent to this module's superior. Users of the RCS-Design tool will ussually find it more convenient to use the variable that is automatically created of the form _status. This variable points to the same location but has a module-specific type.

Last Modified: %H%

If you have questions or comments regarding this page or you would like to be notified of changes to the RCS library via email, please contact Will Shackleford at shackle [at] cme.nist.gov (shackle[at]cme[dot]nist[dot]gov)

Created July 11, 2014, Updated January 6, 2017