.. _guide/parameters: Parameters ========== .. highlight:: cpp .. default-domain:: cpp Parameters allow to view and modify data from running programs. They consist of an input and output signal where the output signal can be overridden either from the ln manager or from python clients. C++ Parameter Providers ----------------------- Creating C++ Parameters is a two-step process. You first need to instantiate a parameter block .. cpp:function:: ln::parameters::parameter_block(ln::client *clnt_, std::string parameter_group_name_, bool always_publish_ = false, std::string topic_name_ = "") and then register parameters to it using .. cpp:function:: template ln::parameters::parameter_block::register_parameters(port_description... port_descriptions) Note that this function can only be called once, you cannot register more than one set of parameters to a `parameter_block`. It is easiest to supply brace-enclosed initializer lists to this functions, e.g. `{"sig_name", &sig_in, &sig_out, before_update_callback, after_update_callback}`. The callbacks can also be omitted and are explained in the next section. Please see the following code example for details. Inside your main loop, you then have to cyclically call .. cpp:function:: ln::parameters::parameter_block::update() to allow overriding parameters. .. sourcecode:: c++ #include #include #include #include #include #include int main(void) { // Init the client ln::client clnt("ln_parameters_example"); // Create parameter block ln::parameters::parameter_block params(&clnt, "ln.parameters.example.cpp"); // Signal double sig_in = 0, sig_out = 0; params.register_parameters({"sig", "human-readable sig description", &sig_in, &sig_out}); // Loop while (true) { std::this_thread::sleep_for(std::chrono::seconds(5)); sig_in += 1; params.update(); std::cout << "sig input: " << sig_in << ", sig output: " << sig_out << std::endl; } } Parameter Callbacks ^^^^^^^^^^^^^^^^^^^ As mentioned above, you can supply the two callbacks .. cpp:struct:: ln::parameters::port_description .. cpp:member:: std::function before_update_callback .. cpp:member:: std::function after_update_callback where the function `before_update_callback` is called from the service handler when a new override arrives. This function can be used to check the parameter value for validity, if the value is e.g. out of range it can `return false;` and the update is rejected. Otherwise it has to `return true;` to accept the update. The function `after_update_callback` is called within your main loop's call to `update()` after a parameter has been updated. It can be used to recompute dependant values. Python Parameter Providers -------------------------- .. index:: single: Python Parameter Providers single: Python parameter provider single: Python parameter block single: client.parameter_block single: ParameterBlock single: ParameterSignal single: ParameterOutputs Search keywords: ``Python Parameter Providers``, ``Python parameter provider``, ``Python parameter block``, ``client.parameter_block``, ``ParameterBlock``, ``ParameterSignal``, ``ParameterOutputs``. Python clients can provide parameter blocks with the same update semantics as the C++ API. Create one block per parameter namespace, add all signals during setup, then call ``update()`` once in each provider loop iteration. The value passed to ``add()`` is used to infer the LN datatype and shape. Scalars, vectors, and matrices can be given as Python values, Python sequences, or NumPy arrays. Use ``dtype=`` when the inferred type is not the one you want. Signal names that are valid Python identifiers can be accessed as attributes, for example ``paramsA.sig1.input`` and ``paramsA.sig1.output``. Bracket access such as ``paramsA["sig1"].input`` always works and is useful for names that are not valid Python attributes. ``update()`` returns a ``ParameterOutputs`` object, so ``outputs.sig1`` and ``outputs["sig1"]`` both refer to the same output value. ``before_update`` is called when a parameter user requests a new override. It receives the requested value and must return ``True`` to accept it or ``False`` to reject it. ``after_update`` is called from the provider's ``update()`` after an accepted override has changed the output value. The Python implementation serializes access to a parameter block, so ``update()`` and signal input/output access may be called from different Python threads. Do not call ``update()`` recursively from a callback. .. code-block:: python import sys import time import numpy as np import links_and_nodes as ln clnt = ln.client("python parameter provider", sys.argv) def accept_sig1(value): return 0.0 <= value <= 100.0 def after_sig1(value): print("sig1 override changed to %.3f" % value) def accept_q(value): return np.all(np.abs(value) < 1000.0) q = np.zeros(43, dtype=np.float64) J = np.zeros((6, 7), dtype=np.float64) with clnt.parameter_block("example.paramsA") as paramsA: paramsA.add( "sig1", 0.0, description="measured scalar with override", before_update=accept_sig1, after_update=after_sig1, ) paramsA.add("enabled", True, description="boolean state") paramsA.add("q", q, description="joint vector", before_update=accept_q) paramsA.add("J", J, description="matrix") start = time.time() while True: t = time.time() - start paramsA.sig1.input = t paramsA.q.input[:] = np.linspace(t, t + 42.0, 43, dtype=np.float64) J[:, :] = np.arange(42, dtype=np.float64).reshape(6, 7) + t outputs = paramsA.update( enabled=True, J=J, ) sig1_cmd = paramsA.sig1.output q_cmd = outputs.q time.sleep(0.01) The block is registered automatically when the ``with`` block exits without an exception. If you do not use a context manager, call ``paramsA.register()`` after adding all signals and before the first ``update()``. Supported dtypes are ``float64``/``double``, ``float32``/``float``, signed and unsigned integer types from 8 to 64 bit, and ``bool``. Debugging Parameters -------------------- The env variable `LN_PARAMETER_DEBUG` can be set to enable some debug output when using ln parameters (also for Simulink models). Manager and Web UI Usage ------------------------ When a client registers a parameter block, it provides manager-visible ``ln/parameters/*`` services. The LN manager GTK UI and web UI use these services to query parameter values, set or clear overrides, and request temporary publication of a parameter as a topic for plotting. In the browser UI, open ``/lnm/parameters`` or select the ``parameters`` tab. Use the pattern field and ``refresh`` button to query providers. Selecting a parameter shows its input, override, and output values. Scalars, vectors, and matrices get editable layouts suited to their shape. ``set override`` fixes the parameter output to the entered value; ``reset override`` returns it to normal provider behavior. ``open scope`` requests topic publication for that parameter and opens ``ln_scope`` for the parameter's ``.output`` field. For scripted access, use the web UI protocol messages documented in :doc:`reference_webui_protocol` or the helper methods on ``links_and_nodes_manager_client.ManagerClient``: .. code-block:: python parameters = await client.get_parameters("*") await client.set_parameter_override( service, parameter_name, True, "42.0", provider_id=provider_id, ) await client.open_parameter_scope( service, parameter_name, provider_id=provider_id, )