XSettings provides a basic persistent storage backend for a specific set of variables in open62541 based OPC/UA servers. It is implemented in the simplest way I know of, i.e. directly mmap()ing the struct containing all variable values to a binary file. Any time a variable changes, the mapped memory is msync()ed asynchronously, so this code can be used in real-time contexts.
To get the introspection data needed, the code heavily leverages the preprocessor using the X-Macro technique (hence the X in XSettings). This in turn means this project cannot be a library: the source code must be embedded in some way into your project.
The typical way of doing that is:
- copy
xsettings.c
andxsettings.h
into your source folder; - define your schema by creating
xsettings-schema.h
; - call the relevant XSettings APIs from your code;
- integrate your build system accordingly.
Refer to the demo
folder for a basic example. Actually UA_Boolean
,
UA_Int32
, UA_UInt32
, UA_Double
and UA_String
(up to 255 bytes)
types are implemented.
When you call xsettings_register()
, XSettings populates the folder
you specified with all the settings you defined in your schema. Their
last values will be remembered via the mapped binary file.
WARNING! You must create the binary file backing up your XSettings
schema before using them, otherwise xsettings_register()
will fail.
The following shell session highlights the issue using the demo program
incuded in this project as an example:
$ ./xsettings-demo
... Failure: status code is BadNotFound
$ ./xsettings-demo -d
... Failure: status code is BadNotFound
$ ./xsettings-demo -c
... Creating settings
$ ./xsettings-demo -d
#define XSTRING_LEN 255
#define XSETTINGS \
/*NAME TYPE DEFAULT DESCRIPTION */ \
X(Boolean, XBOOLEAN, true, "Boolean flag setting") \
X(Int32, XINT32, -123, "Integer (32 bits) setting") \
X(UInt32, XUINT32, 321, "Unsigned integer (32 bits) setting") \
X(Double, XDOUBLE, -9.876, "Double floating point setting") \
X(String, XSTRING, "String", "String setting, up to 255 chars") \
/* EOF */
$ ./xsettings-demo
... TCP network layer listening on ...
There are only 5 public functions.
XSettings xsettings_new(const char *file)
It must be called before any other function, passing the name of the file to be mapped. If that file does not exist, it can be created by a subsequentxsettings_reset()
call. The resulting opaque pointer must be freed withxsettings_free()
when done.void xsettings_free(XSettings xsettings)
No other XSettings function should be called after.UA_StatusCode xsettings_reset(XSettings xsettings)
It creates the file to be mapped (or overwrites it) using the default values specified inxsettings-schema.h
.UA_StatusCode xsettings_dump(XSettings xsettings)
It dumps to stdout the contents of the mapped file. The format is purposedly compatible withxsettings-schema.h
, so you can easily overwrite it to e.g. update the default values.UA_StatusCode xsettings_register(XSettings xsettings, UA_Server *opcua, UA_NodeId folder)
The real meat of this project: mmap() the file (so it must exists!) and register all fields found in your schema as data source variables under thefolder
node.
To build the demo program you must have open62541
preinstalled. Then
you can compile it in the usual meson
way:
$ meson setup build
...
$ meson compile -C build
...
$ cd build/demo
$ ./xsettings-demo -c
... Creating settings
$ ./xsettings-demo
...