event-driven config file parsing

K

Thread Starter

Ken Irving

Hi list,

The recent discussions about config file parsing got me thinking about an event-driven approach, where the modules register which entries they're
interested in, and the parser sends the modules that information via callbacks.

I have implemented this for XML-formatted config files, using the (GPL) expat XML parser, and would like to contribute it if it might be of use.
If XML is not desired as the native config file format, the technique might still be used, e.g., by preprocessing the native config file into XML as an interim measure, or reimplementing the back-end parser functionality using scanf, etc. The advantage (I think) of this approach is to remove all or most parsing concerns from the base and
application modules, and provide a simple API for the purpose.

The called-back module would still need to type-convert and validate the returned data values, but some utility functions might be written
to simplify those tasks.

Here's the API (from confparser.h):

typedef enum {END, ELEM, ATTR} cbtypes; // 3 types of callbacks

typedef void (*parser_callback)(const char *xpath, cbtypes cbtype,
const char *tag, const char *val);

extern int register_callback(const char *xpath, parser_callback cb);

extern int parse();

In the callback, data is returned in the strings tag and val, where tag is a field name in a table identified by the string xpath. The xpath string is a slash-delimited path.

For an example, I expressed the io/modbus/rtu/linuxplc.conf in XML (an
abbreviated config file is included below), then created a stub base module to get callbacks from the SMM section, and a stub "application"
module to get callbacks from the modbus_rtu section. The base module defines its callback functions, and registers its callbacks in an init() function:

void smm_base(const char *xpath, cbtypes cbtype, const char *tag,
const char *val) {
printf("EXAMPLE: %s %s=\"%s\"\n", xpath, tag, val);
}

void cb_base(const char *xpath, cbtypes cbtype, const char *tag,
const char *val) { ... }

int init_base() {
register_callback("/LinuxPLC/SMM", smm_base);
register_callback("/LinuxPLC/SMM/point", cb_base);
return 1;
}

The application module does the same, and also invokes the base init(), and finally runs the parser:

int appstructure = 0;
void cb_appl(const char *xpath, cbtypes cbtype, const char *tag,
const char *val) {
if (!appstructure) { // prepare for new structure
...
}
if (cbtype == END) { /* handle end of branch */
printf("%s: END\n", xpath);
appstructure = 0;
}
... // process conf data based on the xpath and tag
}


int main(int argc, char **argv) {
register_callback("/LinuxPLC/modbus_rtu", cb_appl_comment);
register_callback("/LinuxPLC/modbus_rtu/node", cb_appl);
register_callback("/LinuxPLC/modbus_rtu/io_point", cb_appl);

init_base();

if (!parse()) {
fprintf(stderr, "failed to parse\n");
exit(-1);
}
... // start the PLC (if not already started via callbacks)
return 0;
}

With the callbacks simply printing out the data, this output is produced:

cb_base: /LinuxPLC/SMM sem_key="42"
/LinuxPLC/SMM/point: NEW
cb_base: /LinuxPLC/SMM/point: tag=mcn1filsol
cb_base: /LinuxPLC/SMM/point: descriptor=machine 1 fill solenoid
cb_base: /LinuxPLC/SMM/point: module=some_module
cb_base: /LinuxPLC/SMM/point: addr=0.0
cb_base: /LinuxPLC/SMM/point: END
/LinuxPLC/SMM/point: NEW
cb_base: /LinuxPLC/SMM/point: tag=modbus_rtu.quit.err
cb_base: /LinuxPLC/SMM/point: descriptor=io error status
cb_base: /LinuxPLC/SMM/point: module=modbus_rtu
cb_base: /LinuxPLC/SMM/point: addr=999.15
cb_base: /LinuxPLC/SMM/point: END
cb_base: /LinuxPLC/SMM =""
/LinuxPLC/modbus_rtu/node: NEW
cb_appl: /LinuxPLC/modbus_rtu/node: name=machine_outputs
cb_appl: /LinuxPLC/modbus_rtu/node: unit=1
cb_appl: /LinuxPLC/modbus_rtu/node: type=dout
cb_appl: /LinuxPLC/modbus_rtu/node: port=/dev/ttyS1
cb_appl: /LinuxPLC/modbus_rtu/node: baud=9600
cb_appl: /LinuxPLC/modbus_rtu/node: parity=odd
cb_appl: /LinuxPLC/modbus_rtu/node: END
/LinuxPLC/modbus_rtu/io_point: NEW
cb_appl: /LinuxPLC/modbus_rtu/io_point: name=mcn1filsol
cb_appl: /LinuxPLC/modbus_rtu/io_point: node=machine_outputs
cb_appl: /LinuxPLC/modbus_rtu/io_point: addr=101
cb_appl: /LinuxPLC/modbus_rtu/io_point: update_method=poll
cb_appl: /LinuxPLC/modbus_rtu/io_point: fail_state=last
cb_appl: /LinuxPLC/modbus_rtu/io_point: END
/LinuxPLC/modbus_rtu/io_point: NEW
cb_appl: /LinuxPLC/modbus_rtu/io_point: name=mcn1temp
cb_appl: /LinuxPLC/modbus_rtu/io_point: node=analog_inputs
cb_appl: /LinuxPLC/modbus_rtu/io_point: addr=102
cb_appl: /LinuxPLC/modbus_rtu/io_point: update_method=poll
cb_appl: /LinuxPLC/modbus_rtu/io_point: fail_state=last
cb_appl: /LinuxPLC/modbus_rtu/io_point: END
cb_appl: /LinuxPLC/modbus_rtu =""



A test/example config file (based on io/modbus/rtu/linuxplc.conf):

<?xml version="1.0" ?>
<LinuxPLC> <!-- This is the plc config file. -->

<SMM>
<sem_key>42</sem_key>

<point>
<tag>mcn1filsol</tag>
<descriptor>machine 1 fill solenoid</descriptor>
<module>some_module</module>
<addr>0.0</addr>
</point>

<point>
<tag>modbus_rtu.quit.err</tag>
<descriptor>io error status</descriptor>
<module>modbus_rtu</module>
<addr>999.15</addr>
</point>

</SMM>

<modbus_rtu>

<node>
<name>machine_outputs</name>
<unit>1</unit>
<type>dout</type>
<port>/dev/ttyS1</port>
<baud>9600</baud>
<parity>odd</parity>
</node>

<io_point>
<name>mcn1filsol</name>
<node>machine_outputs</node>
<addr>101</addr>
<update_method>poll</update_method>
<fail_state>last</fail_state>
</io_point>

<io_point>
<name>mcn1temp</name>
<node>analog_inputs</node>
<addr>102</addr>
<update_method>poll</update_method>
<fail_state>last</fail_state>
</io_point>

</modbus_rtu>

</LinuxPLC>


--
Ken Irving <[email protected]>


_______________________________________________
LinuxPLC mailing list
[email protected]
http://linuxplc.org/mailman/listinfo/linuxplc
 
Top