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: .. code:: lua 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 # 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: .. code:: lua 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: .. code:: lua { 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 .. code:: sh $ 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. .. code:: lua node_configurations = { global_rnd_conf = { type = "struct random_config", config = { min=333, max=999 }, } } These configurations can then be assigned to multiple blocks: .. code:: lua { 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: .. code:: lua { src="blkA.portX", tgt="blkB.portY", type="lfrb", config = { ... } - both ``src`` and ``tgt`` are of the form ``CBLOCK.PORT``. Both blocks and ports must exist. - ``type`` specifies the type of iblock to create for the connection. If unset it defaults to ``ubx/lfrb`` - ``config`` is the optional configuration to apply to the newly created iblock. The configs ``type_name`` and ``data_len`` are set automatically unless specified. cblock to iblock ^^^^^^^^^^^^^^^^ The following examples illustrates creating connections to/from an *existing* iblock ``myMq``: .. code:: lua { 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.PORT`` - ``type`` and ``config`` must 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: .. code:: lua { src="blkX.portZ", type="ubx/mqueue", config={ buffer_len=32 } } - ``type`` must be set to desired iblock type and one of ``src`` or ``tgt`` must be unset - ``type_name``, ``data_len`` and ``buffer_len`` are set automatically unless defined in ``config``. - for type ``ubx/mqueue``: if no ``mq_id`` is set in ``config``, then ``mq_id`` is set to the corresponding peer "BLOCK.PORT", e.g. to ``blkX.portZ`` in 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. .. code:: lua 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 [#f1]_ 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: .. code:: lua 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``: .. code:: lua 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: .. code:: lua 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: Merging subsystems ~~~~~~~~~~~~~~~~~~ It is possible to add a subsystem without a namespace, as shown by the following snippet: .. code:: lua 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``: .. code:: sh ubx-launch -webgraph -c deep_composition.usc,ptrig.usc **Note**: unlike merging from within the usc using an unnamed ``subsystems`` entry (see :ref:`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 :download:`c-launch.c <../../examples/C/c-launch.c>` is available under ``examples/C/`` (see the ``README`` for further details). For a more complete example, checkout the respective tutorial section :ref:`c-deployment`. 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. .. rubric:: Footnotes .. [#f1] This feature was introduced in the context of the COCORF RobMoSys Integrated Technical Project. Please see `docs/dev/001-blockdiagram-composition.md `_ for background information.