RHD:Plug-in architecture

From Rsewiki
(Difference between revisions)
Jump to: navigation, search
(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-...)
 
(Update variables)
 
(5 intermediate revisions by one user not shown)
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".
 +
 +
====Initialize device====
 +
 +
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.
 +
 +
====Create database 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.
 +
 +
====Update variables====
 +
 +
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 varidx, int value_index);
 +
 +
The ''varidx'' integer is the index from when the ''write'' variable was created, and the ''value_index'' is 0 if there is just one value, or the index to the value, if the variable is an array.
 +
 +
===test the plugin===
 +
 +
Use rhdtest to test the plugin.
 +
 +
The rhdtest could look something like:
 +
 +
  ******************* RHD Client V1.0 ***************************
 +
  r: (1) tick[1]: (15712)
 +
  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

Latest revision as of 12:11, 18 September 2011

Contents

[edit] 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.

[edit] 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.

[edit] 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".

[edit] Initialize device

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.

[edit] Create database 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.

[edit] Update variables

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 varidx, int value_index);

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

[edit] test the plugin

Use rhdtest to test the plugin.

The rhdtest could look something like:

  ******************* RHD Client V1.0 ***************************
  r: (1) tick[1]: (15712)
  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
Personal tools
Namespaces

Variants
Actions
Navigation
Toolbox