Composing microblx systems
Building a microblx application typically involves instantiating blocks, configuring and interconnecting their ports and finally starting all blocks. The recommended way to do this is by specifying the system using the microblx composition DSL.
Microblx System Composition DSL (usc files)
usc are declarative descriptions of microblx systems that can be
validated and instantiated using the ubx-launch tool. A usc model
describes one microblx system, as illustrated by the following
minimal example:
local bd = require("blockdiagram")
return bd.system
{
-- import microblx modules
imports = {
"stdtypes", "ptrig", "lfrb", "myblocks",
},
-- describe which blocks to instantiate
blocks = {
{ name="x1", type="myblocks/x" },
{ name="y1", type="myblocks/y" },
{ name="ptrig1", type="ubx/ptrig" },
...
},
-- connect blocks
connections = {
{ src="x1.out", tgt="y1.in" },
{ src="y1.out", tgt="x1.in", buffer_len=16 },
},
-- configure blocks
configurations = {
{ name="x1", config = { cfg1="foo", cfg2=33.4 } },
{ name="y1", config = { cfgA={ p=1,z=22.3 }, cfg2=33.4 } },
-- configure a trigger
{ name="trig1", config = { period = {sec=0, usec=100000 },
sched_policy="SCHED_OTHER",
sched_priority=0,
chain0={
-- the #<blockname> directive will
-- be resolved to an actual
-- reference to the respective
-- block once instantiated.
-- every=N triggers the block only
-- every N-th step (default: 1).
{ b="#x1", num_steps=1, every=1, measure=0 },
{ b="#y1", num_steps=1, every=2, measure=0 } } } }
},
}
ptrig additionally supports affinity (CPU list), stacksize,
thread_name, autostop_steps, and sleep_mode (0=OS sleep,
1=busy-wait). The sched_policy field accepts SCHED_OTHER
(default), SCHED_FIFO, SCHED_RR, and SCHED_DEADLINE (Linux
≥ 3.14); for SCHED_DEADLINE a sched_deadline config of type
struct ptrig_deadline is required (see the README for details). See
the trig/ptrig block README
for the full configuration reference.
Lua blocks
Blocks implemented in Lua using the luablock module can be
instantiated directly in a composition using the luablock: type
prefix:
blocks = {
{ name="myblk", type="luablock:myluablock" },
},
When the blockdiagram module encounters a block type with the
luablock: prefix, it uses the luablock-util module to locate
and instantiate the Lua block. The name after the colon (e.g.
myluablock) is resolved by searching for the corresponding
.lua file in the standard microblx block prefixes. This means
there is no need to manually import the luablock module, create a
ubx/luablock instance, or configure lua_file with an absolute
path.
Alternatively, short blocks can be defined inline using the
lua_str config instead of lua_file.
Luablocks support built-in self-triggering via the thread and
period configs — no separate ptrig is required for non-RT
use-cases:
{ name="myblk", type="luablock:myluablock",
config = { thread=1, period=100 } } -- period in milliseconds
Launching
usc files like the above example can be launched using ubx-launch
tool. Run with -h for further information. The following
example
$ cd /usr/local/share/ubx/examples/usc/pid/
$ ubx-launch -webgraph -c pid_test.usc,ptrig_nrt.usc
...
will launch the given system composition and additionally start the
webgraph block. Browse to http://localhost:8888 to see a
live graph of blocks and connections.
Unless the -nostart option is provided, all blocks will be
initialized, configured and started. ubx-launch handles this in
safe way by starting up active blocks after all other blocks (In
earlier versions, there was start directive to list the blocks to
be started, however now this information is obtained by means of the
block attributes BLOCK_ATTR_ACTIVE and BLOCK_ATTR_TRIGGER.)
Node configs
Node configs allow to assign the same configuration to multiple blocks. This is useful to avoid repeating global configuration values that are identical for multiple blocks.
The node_configurations keyword allows to define one or more named
node configurations.
node_configurations = {
global_rnd_conf = {
type = "struct random_config",
config = { min=333, max=999 },
}
}
These configurations can then be assigned to multiple blocks:
{ name="b1", config = { min_max_config = "&global_rnd_conf"} },
{ name="b2", config = { min_max_config = "&global_rnd_conf"} },
Please refer to examples/systemmodels/node_config_demo.usc for a
full example.
Connections
The powerful connections keyword supports connecting blocks in
multiple ways:
cblocks to cblocks
cblocks to iblocks
cblocks to non-existing iblocks (the latter are created on the fly)
The syntax for these variants is discussed below.
cblock to cblock connections
The following example shows how to create ports among cblock ports:
{ src="blkA.portX", tgt="blkB.portY", type="lfrb", config = { ... }
both
srcandtgtare of the formCBLOCK.PORT. Both blocks and ports must exist.typespecifies the type of iblock to create for the connection. If unset it defaults toubx/lfrbconfigis the optional configuration to apply to the newly created iblock. The configstype_nameanddata_lenare set automatically unless specified.
cblock to iblock
The following examples illustrates creating connections to/from an
existing iblock myMq:
{ src="blkX.portZ", tgt="myMQ" }
-- or
{ src="myMQ", tgt="blkX.portZ" }
the iblock must exist and be of the form
IBLOCK(i.e. no port).the cblock must exist and be of the form
CBLOCK.PORTtypeandconfigmust not be set (they will be ignored with a warning).
cblock to non-existing iblock
The following example creates a new mqueue with an automatic, unique
name, configures it with config and connect blkX.portZ to it:
{ src="blkX.portZ", type="ubx/mqueue", config={ buffer_len=32 } }
typemust be set to desired iblock type and one ofsrcortgtmust be unsettype_name,data_lenandbuffer_lenare set automatically unless defined inconfig.for type
ubx/mqueue: if nomq_idis set inconfig, thenmq_idis set to the corresponding peer “BLOCK.PORT”, e.g. toblkX.portZin the example above.
This form is useful to create one-line connections via mqueues or similar.
External blocks
The extern_blocks keyword declares blocks that are expected to
already exist in the node when the composition is launched. This is
useful when loading an auxiliary composition into a running node (e.g.
via the LoadUSCLua or LoadUSCJSON lsdb interface methods) that
needs to connect to blocks from the already running core composition.
return bd.system
{
imports = { "stdtypes", "lfrb", "myblocks" },
extern_blocks = { "core_sensor", "core_actuator" },
blocks = {
{ name="controller", type="myblocks/ctrl" },
},
connections = {
{ src="core_sensor.out", tgt="controller.in" },
{ src="controller.out", tgt="core_actuator.in" },
},
configurations = {
{ name="controller", config = { gain=1.5 } },
},
}
Blocks listed in extern_blocks can be referenced in
connections just like blocks defined in blocks. Validation
will reject references to blocks that are in neither blocks nor
extern_blocks, so typos are still caught at model checking time.
Hierarchical compositions
Using hierarchical composition [1] an application can be composed from other compositions. The motivation is to permit reuse of the individual compositions.
The subsystems keyword accepts a list of namespace-subsystem
entries:
return bd.system {
import = ...
subsystems = {
subsys1 = bd.load("subsys1.usc"),
subsys2 = bd.load("subsys1.usc"),
}
}
Subsystem elements like configs can be accessed by higher levels by
adding the subsystem namespace. For example, the following lines
override a configuration value of the blk block in subsystems
sub11 and sub11/sub21:
configurations = {
{ name="sub11/blk", config = { cfgA=1, cfgB=2 } },
{ name="sub11/sub21/blk", config = { cfgA=5, cfgB=6 } },
}
Note how the subsystem namespaces prevent name collisions of the two identically names blocks. Similar to configurations, connections can be added among subsystems blocks:
connections = {
{ src="sub11/sub21/blk.portX", tgt="sub11/blk.portY" },
},
When launched, a hierarchical system is instantiated in a similar way to a non-hierarchical one, however:
modules are only imported once
blocks from all hierarchy levels are instantiated, configured and started together, i.e. the hierarchy has no implications on the startup sequence.
microblx block names use the fully qualified name including the namespace. Therefore, the #blockname syntax for resolving block pointers works just the same.
if multiple configs for the same block exist, only the highest one in the hierarchy will be applied.
node configs are always global, hence no prefix is required. In case of multiple identically named node configs, the one at the highest level will be selected.
Merging subsystems
It is possible to add a subsystem without a namespace, as shown by the following snippet:
return bd.system {
subsystems = {
bd.load("subsys1.usc"),
}
}
In this case, the subsys1.usc system will be merged directly into
the parent system. Note that entries of the parent system take
precedence, so in case of conflicts elements of the subsystem will be
skipped.
This feature is useful to avoid an extra hierarchy level.
Model mixins
To obtain a reusable composition, it is important to avoid introducing
platform specifics such as ptrig blocks and their
configurations. Instead, passive trig blocks can be used to
encapsulate the trigger schedule. ptrig or similar active blocks
can then be added at launch time by merging them (encapsulated in an
usc file) into the primary model by specifying both on the
ubx-launch command line.
For example, consider the example in
examples/systemmodels/composition:
ubx-launch -webgraph -c deep_composition.usc,ptrig.usc
Note: unlike merging from within the usc using an unnamed
subsystems entry (see Merging subsystems), models merged on
the command line will override existing entries.
Alternatives
Although using usc model is the preferred approach, there are
others way to launch a microblx application:
Launching in C
It is possible to avoid the Lua scripting layer entirely and launch an
application in C/C++. A small self-contained example
c-launch.c is available under
examples/C/ (see the README for further details).
For a more complete example, checkout the respective tutorial section Deployment via C program. Please note that such launching code is a likely candidate for code generation and there are plans for a usc-to-C compiler. Please ask on the mailing if you are interested.
Lua scripts
One can write a Lua “deployment script” similar to the
ubx-launch. Checkout the scripts in the tools section. This
approach not recommended under normally, but can be useful in specific
cases such as for building dedicated test tools.
Footnotes