[toc]
compage is a component-based system architecture implementation framework. It essentially provides means for implementing an efficient thread/process-based black-board programming pattern, i.e. the overall system consists of multiple components that can be instantiated as required.
The framework can be used to "register" different software components that are further instantiated and configured according to the configuration described in .ini files. Such an approach enables efficient system-level experimentation, i.e. one system implementation can be utilized together with a set of configuration files to enable different component interactions and setups, e.g. test simple acquisition-display setup, verify different processing algorithms with data in real-time, and switch between recorded and sensor data. Note that compage does not handle inter-component communications.
The framework can be characterized by its:
- lightweight codebase,
- efficiency,
- minimal dependencies (pthread library),
- compatibility with C and C++ codebase.
The framework heavily relies on preprocessor macro functions and a particular feature of the Executable Linked Files (.elf) - custom data sections that can be parsed during execution. By registering a component, the framework essentially stores its unique id and corresponding handlers, default data and parameters configurable via .ini file. Consult the following diagram for an approximate memory layout.
The compage framework utilizes cmake build system for its compilation and installation.
Build framework with:
$ mkdir build && cd build
$ cmake ..
$ make
Install framework with:
# make install
Simplifying, the executable holds optional init (for initialization), loop (for repeated execution), exit (for deinitialization) callbacks and corresponding default argument, which is a pointer to a C-struct. Further, the framework parses the .ini file to decide on the instantiation of the components and configuration to be updated in the default C-struct before handing data to the callbacks.
The framework provides a simple and more fine-grained APIs for framework's instantiation. The simplest imaginable usage looks like:
#include <edi/compage.h>
int main(int argc, const char *argv[]){
return compage_main(argc, argv);
}
Default framework's instantiation provides a simple command line interface for listing available components and their configuration, generating and applying configuration files. Consult the examples for different methods of using the framework.
Let's assume a simple component with just an init call:
/* declare component's default configuration */
typedef struct{
const char *example_parameter;
}pdata_t;
/* define component's default configuration */
pdata_t pdata = {
"Hello, compage!"
};
/* define component's init handler (static ensures reuse of function name in other file) */
static compageStatus_t init(pdata_t *p){
printf("%s\n", p);
return COMPAGE_SUCCESS;
}
We can register the component as follows:
/* register component's id (string to show up as ini file's section) */
COMPAGE_REGISTER(example);
/* register component's default (private) data*/
COMPAGE_REGISTER_PDATA(example, pdata);
/* register component's init handler*/
COMPAGE_REGISTER_INIT(example, pdata);
/* add data to be configured via ini file */
COMPAGE_ADD_CONFIG(example, pdata, example_parameter);
Similarly one can register loop and exit callbacks, as well as mark additional parameters for the configuration.
The configuration file follows an INI format specification and every section in the file corresponds to a component that should be instantiated. Nonetheless, each component has a default "enable" parameter that can be used for temporary disabling the component's execution. Each parameter in the section corresponds to the C-struct member. Two instantiations of the previously declared component could look like this:
[example]
enabled=1
example_parameter="String to be displayed upon execution"
[example]
enabled=1
example_parameter="Another string to be displayed upon execution"
Currently supported data types are:
bool
int8_t
uint8_t
int16_t
uint16_t
int32_t
uint32_t
int64_t
uint64_t
float
double
char
char*
const char*
When using compiler optimizations, the compiler can reorder the contents of the compage section, which most probably will result in segmentation fault as the framework depends on the actual layout of the memory. This can be fixed by adding the -fno-toplevel-reorder compilation option.