RHD:Plug-in architecture: Difference between revisions

From Rsewiki
(New page: ===Load a plug-in=== Plugins are loaded when they are mentioned in the ''rhdconfig'' file, e.g. like <?xml version="1.0" ?> <rhd> <sheduler> <period value="3000000"/><!--in usec-...)
 
No edit summary
Line 41: Line 41:


At every sample time, just after data may have been received from weriter clients, a function called ''periodic(int tick)'' is called. This can be used to react on new variable values, or other regular tasks.
At every sample time, just after data may have been received from weriter clients, a function called ''periodic(int tick)'' is called. This can be used to react on new variable values, or other regular tasks.
On exit, when the rhd terminates, the ''terminate()'' function is called, to allow to stop controlled devices and to close
logfiles etc.
====XML-read====
When you make a new rhd-plgin, start with an existing one, and modify as needed.
The first thing is the initialization - the read of the configuration file.
The ''initXML(char * filename)'' can mostly be left as is. This is the place where some configuration data structures can be set with default values.
This should be done in the start of the initXML() function.
Later in ''char * filename'' there is a call to ''XML_parse(...)'', this is where the rhdconfig file is parsed, and most values should be decoded in the ''lsStartTag(void *data, const char *el, const char **attr)''
A (simple) example is in the sf9dof.c plugin from the sparkfun gyro-accelerometer-compas plug-in:
void XMLCALL '''lsStartTag'''(void *data, const char *el, const char **attr)
{ // a start tag is detected
  int i;
  parseInfo *info = (parseInfo *) data;
  info->depth++;
  if (info->depth < info->skip || info->skip == 0)  {
    switch (info->depth)    {
      case 1:
        if (strcmp("rhd",el) == 0) ;
        else info->skip = info->depth;
        break;
      case 2:
        if (strcmp("plugins",el) == 0)
          ; // no attributes here - but at next level
        else
          // skip this group
          info->skip = info->depth;
        break;
      case 3:
        // this one handles sf9dof only
        if (strcmp('''"sf9dof"''',el) == 0)
        { // get enable bit and device name
          const char * att, * val;
          for(i = 0; attr[i]; i+=2) {
            att = attr[i];
            val = attr[i + 1];
            '''if ((strcmp("enable",att) == 0) && (strcmp("true",val) == 0))'''
            '''  info->enable = 1;'''
            '''else if (strcmp("dev", att) == 0) '''
            '''{'''
            '''  char * s = serif.serialDev;'''
            '''  strncpy(serif.serialDev, val, MxDL);'''
            '''  printf("%s\n", s);'''
            '''}'''
            '''else if (strcmp("baudrate", att) == 0)'''
            '''{'''
            '''  serif.baudrate = strtol(val, NULL, 0);'''
            '''}'''
            else if (strcmp("debug", att) == 0)
            {
              serif.debugFlag = strtol(val, NULL, 0);
              if (serif.debugFlag)
                printf("  SF9DOF started in DEBUG mode!\n");
            }
          }
          if (!info->enable)
            printf("  SF9DOF: Use is disabled in configuration\n");
        }
        else
          info->skip = info->depth;
        break;
      default: // unknown tag series
        break;
    }
  }
}
Here the main parameters to extract is the serial device name and baudrate (see the rhdconfig.xml file on top of this page)
they are extracted at "depth level 3" in the tag called "sf9dof".
After the parsing of the XML file the device needs to be initialized.
This is initiated at the end of the ''initXML(char * filename)'' with a call to ''initSf9dof()'':
extern int '''initXML(char *filename)'''
{
  int result;
  parseInfo xmlParse;
  ...
    result = ('''XML_Parse'''(parser, xmlBuf, len, done) != XML_STATUS_ERROR);
  ...
  if (result && xmlParse.enable)
  { // all is fine - start plugin
    result = '''initSf9dof();'''
  }
  return result;
}
The init function should open and configure the devise for operation and create the variables ''write'' and ''read'' that should be exchanged with the rhd clients.
Very often a read thread needs to be started to handle the communication with the device,
The function could look something like:
int'''initSf9dof('''void)
{ //Open first serial port
  int result;
  rxtask.running = 0;
  rxtask.startNewRxCycle = 0;
  // open device
  serif.ttyDev = open(serif.serialDev, O_RDWR /*| O_NONBLOCK*/);
  result = serif.ttyDev != -1;
  if (result == 0)
    fprintf(stderr,"  SF9DOF: Can't open device: %s\n", serif.serialDev);
  ...
  if (result == 1)
  { // start thread to handle bus
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setinheritsched(&attr, PTHREAD_INHERIT_SCHED);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    if (pthread_create(&rxtask.sf9dof_thread, &attr, '''sf9dof_task''', 0))
    { perror("  SF9DOF: Can't start sf9dof receive thread"); result = 0; }
  }
  if (result == 1)
  { /****** Create database variables if all is ok **************/
    '''createSf9dofvariables()''';
    ...
  }
  return result;
}
A read thread ''sf9dof_task(void *)'' is started to communicate with the device, and
a ''createSf9dofvariables()'' is called to create the data exchange variables.
The ''createSf9dofvariables()'' could look like:
void createSf9dofvariables()
{ // version
  sf9dof.varVersion = createVariable('r', 1, "version");
  // accelerometer values
  sf9dof.varAcc = createVariable('r', 3, "sf9acc");
  // gyro values
  sf9dof.varGyro = createVariable('r', 3, "sf9gyro");
  // magnetometer values
  sf9dof.varMag = createVariable('r', 3, "sf9mag");
  // calculated rotation from acc
  sf9dof.varRoll = createVariable('r', 2, "sf9roll");
  // calculated rotation from acc
  sf9dof.varPitch = createVariable('r', 2, "sf9pitch");
  // calculated heading from compas - based on magnetic north
  sf9dof.varCompas = createVariable('r', 2, "sf9compas");
  /// update rate
  sf9dof.varUpdRate = createVariable('r', 1, "sf9updateRate");
}
In this case there is ''read'' variables only, but exchange the 'r' with a 'w' and it becomes a write variable.
An index to the created variables are returned, and they must be used, when the values should be updated (or the ''write'' variables read.
An example that updates the ''sf9gyro'' and ''sf9updateRate'' variable could look like
...
setArray(sf9dof.varGyro, 3, g);
setVariable(sf9dof.varUpdRate, 0, updCnt/5);
...
The first sets all 3 elements if the gyro value, the next sets the one value only.
The timestamp and update flag are set by the calls too.
You can read values from ''write'' variables by the call:
    int  getWriteVariable(int variable, int value_index);
The variable integer is the index from when the ''write'' variable was created, and the ''value_index'' in 0 if there is just one value, or the index to the value, if variable is an array.
===test the plugin===
Use rhdtest to test the plugin.
The rhdtest could look something like:
  ******************* RHD Client V1.0 ***************************
  r: (0) version[1]: (0)
  r: (1) sf9acc[3]: (23)(-3)(-288)
  r: (1) sf9gyro[3]: (373)(374)(385)
  r: (1) sf9mag[3]: (-80)(-320)(1061)                           
  r: (1) sf9roll[2]: (175)(433985)
  r: (1) sf9pitch[2]: (-179)(-403190)
  r: (1) sf9compas[2]: (-165)(-963756)
  r: (1) sf9updateRate[1]: (86)
  w: (0) steeringangleref[1]: (0)
  w: (0) armAxis[6]: (0)(0)(0)(233)(0)(0)
  w: (0) speedl[1]: (25)
  w: (0) speedr[1]: (0)
  w: (0) reset[1]: (0)                           
  Host: mercury    Access: w    Database: 33 r / 8 w  Period: 0.033 s
  >> _
''write'' variables can be set with a commands like:
>> set speedl=25
>> set armAxis[3]=233

Revision as of 12:05, 18 September 2011

Load a plug-in

Plugins are loaded when they are mentioned in the rhdconfig file, e.g. like

<?xml version="1.0" ?>
<rhd>
 <sheduler>
   <period value="3000000"/>
   <type value="itimer"/>
 </sheduler>
 <server>
   <port value="24902"/>
   <clients number="10" allwriters="1"/>
 </server>
 <plugins basepath="/usr/local/smr/lib/rhdplugin/">
   <joycontrol enable="true" lib="libjoycontrol.so.1" critical="false" safety="1">
       <joystick port="/dev/input/js0"/>
   </joycontrol>
   <sf9dof enable="true"
         lib="libsf9dof.so.1"
         critical="false"
         dev="/dev/ttyS1"
         baudrate="38400"
         debug="0">
   </sf9dof>
 </plugins>
</rhd>

Here the plugins section has an attribute basepath that states where the plug-in files are to be found.

Two plugins are loaded in this example, a joycontrol plug-in from the file libjoycontrol.so.1, in the joycontrol XML group is an embedded XML tag, called joystick with the needed configuration data.

Further is a sf9dof plugin, where the plugin configuration data is placed as attributes directly in the sf9dof tag.

Plug-in function

A plugin should read the configuration file, and all relevant configuration values should be controllable from the configuration file.

The plug-in is expected to have a function called initXML(char * filename) that reads the configuration file and initializes the plug-in as needed. If all went well, then the function should return 0.

At every sample time, just after data may have been received from weriter clients, a function called periodic(int tick) is called. This can be used to react on new variable values, or other regular tasks.

On exit, when the rhd terminates, the terminate() function is called, to allow to stop controlled devices and to close logfiles etc.

XML-read

When you make a new rhd-plgin, start with an existing one, and modify as needed.

The first thing is the initialization - the read of the configuration file.

The initXML(char * filename) can mostly be left as is. This is the place where some configuration data structures can be set with default values. This should be done in the start of the initXML() function. Later in char * filename there is a call to XML_parse(...), this is where the rhdconfig file is parsed, and most values should be decoded in the lsStartTag(void *data, const char *el, const char **attr)

A (simple) example is in the sf9dof.c plugin from the sparkfun gyro-accelerometer-compas plug-in:

void XMLCALL lsStartTag(void *data, const char *el, const char **attr)
{ // a start tag is detected
 int i;
 parseInfo *info = (parseInfo *) data;
 info->depth++;
 if (info->depth < info->skip || info->skip == 0)  {
   switch (info->depth)    {
     case 1:
       if (strcmp("rhd",el) == 0) ;
       else info->skip = info->depth;
       break;
     case 2:
       if (strcmp("plugins",el) == 0)
         ; // no attributes here - but at next level
       else
         // skip this group
         info->skip = info->depth;
       break;
     case 3:
       // this one handles sf9dof only
       if (strcmp("sf9dof",el) == 0)
       { // get enable bit and device name
         const char * att, * val;
         for(i = 0; attr[i]; i+=2) {
           att = attr[i];
           val = attr[i + 1];
           if ((strcmp("enable",att) == 0) && (strcmp("true",val) == 0))
             info->enable = 1;
           else if (strcmp("dev", att) == 0) 
           {
             char * s = serif.serialDev;
             strncpy(serif.serialDev, val, MxDL);
             printf("%s\n", s);
           }
           else if (strcmp("baudrate", att) == 0)
           {
             serif.baudrate = strtol(val, NULL, 0);
           }
           else if (strcmp("debug", att) == 0)
           {
             serif.debugFlag = strtol(val, NULL, 0);
             if (serif.debugFlag)
               printf("   SF9DOF started in DEBUG mode!\n");
           }
         }
         if (!info->enable)
           printf("   SF9DOF: Use is disabled in configuration\n");
       }
       else
         info->skip = info->depth;
       break;
     default: // unknown tag series
       break;
   }
 }
}

Here the main parameters to extract is the serial device name and baudrate (see the rhdconfig.xml file on top of this page) they are extracted at "depth level 3" in the tag called "sf9dof".

After the parsing of the XML file the device needs to be initialized. This is initiated at the end of the initXML(char * filename) with a call to initSf9dof():

extern int initXML(char *filename)
{
 int result;
 parseInfo xmlParse; 

 ...

   result = (XML_Parse(parser, xmlBuf, len, done) != XML_STATUS_ERROR);

 ...

 if (result && xmlParse.enable)
 { // all is fine - start plugin
   result = initSf9dof();
 }
 return result;
}

The init function should open and configure the devise for operation and create the variables write and read that should be exchanged with the rhd clients.

Very often a read thread needs to be started to handle the communication with the device,

The function could look something like:

intinitSf9dof(void) { //Open first serial port

 int result;
 rxtask.running = 0;
 rxtask.startNewRxCycle = 0;
 // open device
 serif.ttyDev = open(serif.serialDev, O_RDWR /*| O_NONBLOCK*/);
 result = serif.ttyDev != -1;
 if (result == 0)
   fprintf(stderr,"   SF9DOF: Can't open device: %s\n", serif.serialDev);
 ...
 if (result == 1)
 { // start thread to handle bus
   pthread_attr_t attr;
   pthread_attr_init(&attr);
   pthread_attr_setinheritsched(&attr, PTHREAD_INHERIT_SCHED);
   pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
   if (pthread_create(&rxtask.sf9dof_thread, &attr, sf9dof_task, 0))
   { perror("   SF9DOF: Can't start sf9dof receive thread"); result = 0; }
 }
 if (result == 1)
 { /****** Create database variables if all is ok **************/
   createSf9dofvariables();
   ...
 }
 return result; 

}

A read thread sf9dof_task(void *) is started to communicate with the device, and a createSf9dofvariables() is called to create the data exchange variables.

The createSf9dofvariables() could look like:

void createSf9dofvariables()
{ // version
 sf9dof.varVersion = createVariable('r', 1, "version");
 // accelerometer values
 sf9dof.varAcc = createVariable('r', 3, "sf9acc");
 // gyro values
 sf9dof.varGyro = createVariable('r', 3, "sf9gyro");
 // magnetometer values
 sf9dof.varMag = createVariable('r', 3, "sf9mag");
 // calculated rotation from acc
 sf9dof.varRoll = createVariable('r', 2, "sf9roll");
 // calculated rotation from acc
 sf9dof.varPitch = createVariable('r', 2, "sf9pitch");
 // calculated heading from compas - based on magnetic north
 sf9dof.varCompas = createVariable('r', 2, "sf9compas");
 /// update rate
 sf9dof.varUpdRate = createVariable('r', 1, "sf9updateRate");
}

In this case there is read variables only, but exchange the 'r' with a 'w' and it becomes a write variable. An index to the created variables are returned, and they must be used, when the values should be updated (or the write variables read.

An example that updates the sf9gyro and sf9updateRate variable could look like

...
setArray(sf9dof.varGyro, 3, g);
setVariable(sf9dof.varUpdRate, 0, updCnt/5);
...

The first sets all 3 elements if the gyro value, the next sets the one value only. The timestamp and update flag are set by the calls too.

You can read values from write variables by the call:

    int  getWriteVariable(int variable, int value_index);

The variable integer is the index from when the write variable was created, and the value_index in 0 if there is just one value, or the index to the value, if variable is an array.

test the plugin

Use rhdtest to test the plugin.

The rhdtest could look something like:

  ******************* RHD Client V1.0 ***************************
  r: (0) version[1]: (0)
  r: (1) sf9acc[3]: (23)(-3)(-288)
  r: (1) sf9gyro[3]: (373)(374)(385)
  r: (1) sf9mag[3]: (-80)(-320)(1061)                             
  r: (1) sf9roll[2]: (175)(433985)
  r: (1) sf9pitch[2]: (-179)(-403190)
  r: (1) sf9compas[2]: (-165)(-963756)
  r: (1) sf9updateRate[1]: (86)
  w: (0) steeringangleref[1]: (0)
  w: (0) armAxis[6]: (0)(0)(0)(233)(0)(0)
  w: (0) speedl[1]: (25)
  w: (0) speedr[1]: (0) 
  w: (0) reset[1]: (0)                            

  Host: mercury    Access: w    Database: 33 r / 8 w   Period: 0.033 s

  >> _

write variables can be set with a commands like:

>> set speedl=25
>> set armAxis[3]=233