- Generate C codes from a text configuration file
- Various types of variables, array of values and structured variables are supported
- Statically configured variables and dynamically configured variables
- IPC with Unix Domain mode sockets or UDP sockets
- save and restore persistent variables
This project consists of an executable python script and some template files.
Therefore no build is needed.
For the installation, just run ‘./autogen.sh; ./configure; make install’.
To use in your projects, it requires ‘xl4unibase’
and ‘xl4combase’ libraries to be linked.
‘make check’ generates sample_*.c, sample_*.h and sample_ipcclient.
Looking at ‘sample_defaults.cfg’ and the generated C codes, it will help to understand how the
script works to generate the codes.
‘sample_ipcconfigs_manual_test r’(a parameter ‘r’ on the command line) runs an IPC server. ‘sample_ipcclient’ connects to the server. You can check IPC client functions by this running.
To run a simple example, it is only one file you need to write from scratch.
Write a ‘hello.cfg’ file, which has simple 5 variables as the configuration items.
------------------------------------------------------------
PORT1 1000 IR # port number 1, IPC readable but not changeable
IP1 192,168,1,1 IR # IP Address 1, IPC readable but not changeable
PORT2 1001 IRW # port number 2, IPC readable and changeable
IP2 192,168,1,2 IRW # IP Address 2, IPC readable and changeable
MES "two ports are defined"[128] IRW # 128 char space of a string
------------------------------------------------------------
the first column is a name of a variable.
the second column is a value.\
the third column is IPC flags, IR:IPC readable, IW:IPC writable,\
IRW or I: IPC readable and writable
Run a command as follows:
------------------------------------------------------------
$ up_genconf.py -i hello.cfg --hfile --cfile --ipcfile --cfile --clfile \
--srvfile -m hello.conf -p hello
------------------------------------------------------------
It generate the following files:
------------------------------------------------------------
hello_configs.h
hello_configs.c
hello_ipcconfigs.h
hello_ipcconfigs.c
hello_ipcserver.c
hello_ipcclient.c
------------------------------------------------------------
------------------------------------------------------------
cc -Wall -DUB_LOGCAT=2 -c -o hello_configs.o hello_configs.c
cc -Wall -DUB_LOGCAT=2 -c -o hello_ipcconfigs.o hello_ipcconfigs.c
cc -Wall -DUB_LOGCAT=2 -c -o hello_ipcserver.o hello_ipcserver.c
cc -o hello_server hello_ipcserver.o hello_configs.o hello_ipcconfigs.o \
-lx4unibase -lx4combase -lpthread
cc -Wall -DUB_LOGCAT=2 -c -o hello_ipcclient.o hello_ipcclient.c
cc -o hello_client hello_ipcclient.o hello_configs.o hello_ipcconfigs.o \
-lx4unibase -lx4combase -lpthread
------------------------------------------------------------
A runtime configuration file: hello.conf has been generated the command above. In the file all the items are commented out, so that the application starts with the default values which is configured in the build time configuration file.
As an example, let’s edit ‘hello.conf’ and update as follows:
------------------------------------------------------------
# port number 1, IPC readable but not changeable
#PORT1 1000
# IP Address 1, IPC readable but not changeable
IP1 192,168,1,10
# port number 2, IPC readable and changeable
#PORT2 1001
# IP Address 2, IPC readable and changeable
#IP2 192,168,1,2
# 128 char space of a string
#MES "two ports are defined"
------------------------------------------------------------
This changes ‘IP1’ from ‘192.168.1.1’ to ‘192.168.1.10’.
The runtime configuration file can’t change the IPC flags.
To create a new runtime configuration file from a running application, call ‘hellowrite_config_file’.
------------------------------------------------------------
[server]$ ./hello_server
unibase-1.1.0-392f063
INF:combase:cb_ipcsocket_init:combase-1.1.0-ccfdddb
[client]$ ./hello_client
unibase-1.1.0-392f063
INF:combase:cb_ipcsocket_init:combase-1.1.0-ccfdddb
------------------------------------------------------------
------------------------------------------------------------
([client] hit return)
0:PORT1(R) 1:IP1(R) 2:PORT2(RW) 3:IP2(RW)
4:MES(RW)
q:quit, b:binary mode, t:text mode,
l:read dynamically registered variables, Return:print variable list
ITEM_NUMBER [-i index] [-f field_index] [-u update_value]
------------------------------------------------------------
It shows 5 variables:’0:PORT1(RW) 1:IP1(RW) 2:PORT2(RW) 3:IP2(RW) 4:MES(RW)’
To see the first variable ‘PORT1’, enter ‘0’
------------------------------------------------------------
0
Read: PORT1 1000
------------------------------------------------------------
To see the second variable ‘IP1’, enter ‘1’
------------------------------------------------------------
1
Read: IP1 192,168,1,10
------------------------------------------------------------
To change variable ‘PORT2’ from 1001 to 2001, ‘IP2’ from 192,168,1,2 to 0,0,0,0
------------------------------------------------------------
2 -u 2001
3 -u 0,0,0,0
------------------------------------------------------------
Read back the changed variables,
------------------------------------------------------------
2
Read: PORT2 2001
3
Read: IP2 0,0,0,0
------------------------------------------------------------
Trying to change ‘PORT1’ or ‘IP1’ can’t be successful, becuase they don’t have ‘W’ flag in the build configuration file
Write a ‘helloa.cfg’ file as follows:
------------------------------------------------------------
Variable_A1 0,1,2,3 IRW # array of 4 int32_t integers
Variable_A2 1a:2b:3c:4d:5e:6f IRW # array like a mac address: array of uint8_t integers
Variable_A3 {10:21:32:43:54:65},{1a:2b:3c:4d:5e:6f} IRW # array of 2 mac addresses
Variable_A4 [16]100 IRW # array of 16 integers
------------------------------------------------------------
Building ‘helloa_server’ and ‘helloa_client’ is the same as the way in ‘A Simple Example’ section.
run ‘helloa_server’ and ‘helloa_client’.
the client shows the variable as follows:
------------------------------------------------------------
[client]$ ./helloa_client
unibase-1.1.0-392f063
INF:combase:cb_ipcsocket_init:combase-1.1.0-ccfdddb
0:Variable_A1(RW) 1:Variable_A2(RW) 2:Variable_A3(RW) 3:Variable_A4(RW)
q:quit, b:binary mode, t:text mode,
l:read dynamically registered variables, Return:print variable list
ITEM_NUMBER [-i index] [-f field_index] [-u update_value]
----------
0
Read: Variable_A1 0,1,2,3
1
Read: Variable_A2 1A:2B:3C:4D:5E:6F
2
Read: Variable_A3 {10:21:32:43:54:65},{1A:2B:3C:4D:5E:6F}
3
Read: Variable_A4 100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100
------------------------------------------------------------
Change some of array variables
------------------------------------------------------------
0 -u 0,10,20,30 # this updates all 4 values
0
Read: Variable_A1 0,10,20,30
0 -i 2 -u 200 # this updates the value of index=2
0
Read: Variable_A1 0,10,200,30
2 -i 0 -u 0:1 # this updates the first 2 bytes of index=0
2
Read: Variable_A3 {00:01:32:43:54:65},{1A:2B:3C:4D:5E:6F}
Write a ‘hellob.cfg’ file as follows:
------------------------------------------------------------
# define {double,char[10],char,char,bool, 3 integers, 64-bit integer}
# and assign the values
StructA-Variable_B1 {3.14,"good"[10],'X','Y',true,{3,2,1},100L} IRW
# the same struct, end parts of fields are not defined
StructA-Variable_B2 {2.7,"nice",'a','b',false} IRW
# define new sturct
StructB-Variable_C1 [3]{4000,{192,168,10,1},"p1"} IRW
------------------------------------------------------------
Building ‘hellob_server’ and ‘hellob_client’ is the same as the way in ‘A Simple Example’ section.
run ‘hellob_server’ and ‘hellob_client’. On the client side console, it works as follows:
------------------------------------------------------------
[client]$ ./hellob_client
unibase-1.1.0-392f063
INF:combase:cb_ipcsocket_init:combase-1.1.0-2133d13
0:Variable_B1(RW) 1:Variable_B2(RW) 2:Variable_C1(RW)
q:quit, b:binary mode, t:text mode,
l:read dynamically registered variables, Return:print variable list
ITEM_NUMBER [-i index] [-f field_index] [-u update_value]
----------
0
Read: Variable_B1 {3.140000,"good",'X','Y',true,{3,2,1},100}
1
Read: Variable_B2 {2.700000,"nice",'a','b',false,{0,0,0},0}
2
Read: Variable_C1 {4000,{192,168,10,1},"p1"},{4000,{192,168,10,1},"p1"},{4000,{192,168,10,1},"p1"}
0 -f 1 -u "bad" # this updates Variable_B1.field1 to "bad"
0 -f 6 -u 200 # this updates Variable_B1.field6 to 200
0
Read: Variable_B1 {3.140000,"bad",'X','Y',true,{3,2,1},200}
# update index=1 and index=2 of Variable_C1
2 -i 1 -f 1 -u {192,168,10,2}
2 -i 1 -f 2 -u "p2"
2 -i 2 -f 1 -u {192,168,10,3}
2 -i 2 -f 2 -u "p3"
2
Read: Variable_C1 {4000,{192,168,10,1},"p1"},{4000,{192,168,10,2},"p2"},{4000,{192,168,10,3},"p3"}
------------------------------------------------------------
Open ‘hello_ipcserver.c’; in the main function the code is as follows:
------------------------------------------------------------
...
helloipcserver_init("/tmp/hello_ipcconf", 0, true);
helloipcserver_set_update_cb(ipc_update_cb, NULL);
read(0, &c, 1);
helloipcserver_close();
...
------------------------------------------------------------
It is just waiting an input from console to close the application.
Now, add dynamic registration before the ‘read’.
------------------------------------------------------------
...
helloipcserver_init("/tmp/hello_ipcconf", 0, true);
helloipcserver_set_update_cb(ipc_update_cb, NULL);
{
int32_t p3=1002;
int32_t ip3[4]={192,168,1,3};
helloitem_extend_t eid1={"PORT3", &p3,
{sizeof(int32_t), 1, 1, VT_INT32_T}, UPIPC_RW};
helloitem_extend_t eid2={"IP3", &ip3,
{sizeof(int32_t), 1, 4, VT_INT32_T}, UPIPC_RW};
helloregister_extend_item(&eid1);
helloregister_extend_item(&eid2);
}
read(0, &c, 1);
helloipcserver_close();
...
------------------------------------------------------------
run ‘hello_server’ and ‘hello_client’. On the client side console, it works as follows:
------------------------------------------------------------
[client]$ ./hello_client
unibase-1.1.0-392f063
INF:combase:cb_ipcsocket_init:combase-1.1.0-2133d13
0:PORT1(RW) 1:IP1(RW) 2:PORT2(RW) 3:IP2(RW)
q:quit, b:binary mode, t:text mode,
l:read dynamically registered variables, Return:print variable list
ITEM_NUMBER [-i index] [-f field_index] [-u update_value]
----------
l # 'l' command reads the dynamically registered variables
update 2 dynamically registered variables
0:PORT1(RW) 1:IP1(RW) 2:PORT2(RW) 3:IP2(RW)
10000:PORT3(RW) 10001:IP3(RW) # the dynamically registered variables starts from '10000'
q:quit, b:binary mode, t:text mode,
l:read dynamically registered variables, Return:print variable list
ITEM_NUMBER [-i index] [-f field_index] [-u update_value]
----------
10000
Read: PORT3 1002
10001
Read: IP3 192,168,1,3
10000 -u 2002
10001 -i 2 -u 2
10000
Read: PORT3 2002
10001
Read: IP3 192,168,2,3
------------------------------------------------------------
In the current version, struct variables can’t be registered dynamically.
Look at the generated ‘*_configs.h’ and ‘*_ipcconfigs.h’.
Value definition part can’t include space characters other than inside of double/single quoted area.
- 10 – int32_t type value
- 10Y – int8_t type value
- 10S – int16_t type value
- 10L – int64_t type value
- 0x10 – uint32_t type value in hex
- 0x10Y – uint8_t type value in hex
- 0x10S – uint16_t type value in hex
- 0x10L – uint64_t type value in hex
- 2.71828 – double type value
- 2.71828F – float type value
- ‘a’ – char type value
- ‘\0’ – null
- “abc” – 4 bytes of char (null is added)
- “abc”[10] – 10 bytes of char (null is added at 4th byte)
- true
- false
All elements must have the same type
- 10,20 – int32_t type value array
- 10S,20S – int16_t type value array
- 10:20 – uint8_t type value array in hex format
- 1000:2000 – uint16_t type value array in hex format
- {10,20},{30,40} – int32_t type value 2d-array
- [4]10 – the same as 10,10,10,10
- [4]{10,20} – the same as {10,20},{10,20},{10,20},{10,20}
- ‘a’,’b’,’\0’ – char type value array
- [2]”xyz”[6] – the same as “xyz”[6],”xyz”[6]
struct is registered at the first appearance in the buid time configuration file.
StructA-VariableA definition_assignment: this defines ‘StructA’\
StructA-VariableB definition_assignment: the second appearance must follow already defined StructA
When thre are no field name definitions, ‘f0’,’f1’,’f2’,,, are automatically defined.
------------------------------------------------------------
StructA:fname fieldA,fieldB,fieldC
------------------------------------------------------------
This defines ‘fieldA’, ‘fieldB’, and ‘fieldC’ as the first 3 field names. If there are more fields, ‘f3’,’f4’,,, are used to continue.
The field name definition can be either before or after the struct definition.
- {variable1,variable2,,,}
- [3]{variable1,variable2,,,} – 3 array of the struct
- VariableA Value1 – assign a new ‘value1’ on ‘VariableA’
- VariableB[0] Value2 – assign a new ‘value2’ on ‘VariableB’ index=0
- VariableB[1] Value3 – assign a new ‘value3’ on ‘VariableB’ index=1
- VariableC.f2 Value4 – assign a new ‘value4’ on ‘VariableC’ field index=2
- VariableD[2].f1 Value5 – assign a new ‘value5’ on ‘VariableD’ index=2 and field index=1
In examples above, the IPC server uses threading mode.
To run in non-threading mode, add ‘–ntfile’ with ‘up_genconf.py’. For the first simple example, the command runs as follows:
------------------------------------------------------------
$ up_genconf.py -i hello.cfg --hfile --cfile --ipcfile --cfile --clfile \
--srvfile -m hello.conf -p hello --ntfile
------------------------------------------------------------
It additionary generates ‘hello_non_thread.h’. Use this file in the compiler parameters as follows:
$ cc -DCOMBASE_NO_THREAD include hello_non_thread.h ...
The IPC server program is responsible to call ‘helloipcserver_receive’ with catching an read event on the fd.
The fd can be got by calling ‘helloget_ipcfd’
When ‘UNIPAC_PERSISTENT’ is defined in the config file, the data is saved in a file defined as ‘UNIPAC_PERSISTENT’ To be a persistent variable, add ‘P’ flag on the third column. An example is as follows:
------------------------------------------------------------
UNIPAC_PERSISTENT "/var/tmp/xl4unipac_sample"
VALUE_A 1000 P # VALUE_A is persistent
------------------------------------------------------------
After reading from a config file, call ‘*persistent_restore();’ and before terminating the program, call ‘*persistent_save();’
An example is as follows:
------------------------------------------------------------
sampleread_config_file("sample_defaults.conf");
samplepersistent_restore();
...
update variables
...
samplepersistent_save();
------------------------------------------------------------
For the variables with ‘P’ flag, the updated contents are restored and the values are persistent. To reset the values, delete the file defined by UNIPAC_PERSISTENT.
‘UNIPAC_PERSISTENT’ is configurable at runtime by defining in the conf file.