RHD:Plug-in architecture: Difference between revisions
(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-...) |
|||
(5 intermediate revisions by the same 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
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".
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.
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