Source code for state_space_setup_assistant.ros2_control_export
"""Build the C++-consumable ``<name>_ros2_control.yaml`` artifact.
``controller.npz`` (written by ``ControllerResult.save_npz``) carries the
plant/controller matrices but not ``joint_names``/``q_eq`` -- those live only
on the linearized ``StateSpaceModel``. A ros2_control controller needs both
(to turn a raw measured joint position into the deviation state the gain/
compensator was designed for), so this module builds one flat dict from the
model *and* the result together, meant to be dumped with
``yamlgen.dump_yaml`` right after ``result.save_npz`` in ``export.py``.
Schema (flat nested lists only, so a C++ ``yaml-cpp`` reader needs no NumPy
support):
schema_version: 1
controller_type: lqr # or lqg / hinf / hinf_mixsyn / pid
name: lqr
joint_names: [...] # full state order: x = [q - q_eq, qdot]
q_eq: [...]
actuated_joint_names: [...] # order matches K's rows / u
u_eq: [...]
output_names: [...] # used only by dynamic-compensator designs
K: [[...], ...] # present iff a static-gain design
ctrl_A/ctrl_B/ctrl_C/ctrl_D: [[...], ...] # present instead of K otherwise
info: {...} # informational scalars only, not consumed
"""
from typing import Dict
import numpy as np
def _tolist(arr) -> list:
return np.asarray(arr, dtype=float).tolist()
def _info_dict(info: Dict) -> Dict:
out = {}
for key, val in info.items():
if not np.isscalar(val):
continue
try:
out[key] = float(val)
except TypeError:
continue # e.g. complex scalars: not yaml-safe, informational only
return out
[docs]
def ros2_control_yaml_dict(model, result) -> Dict:
"""Build the ``*_ros2_control.yaml`` dict from a ``StateSpaceModel``
(``session.model``) and a ``ControllerResult`` (``session.result``).
Exactly one of ``result.K`` / ``result.controller`` must be set, matching
``ControllerResult``'s own invariant.
"""
if result.K is None and result.controller is None:
raise ValueError(
'result has neither a static gain nor a dynamic controller')
cfg = {
'schema_version': 1,
'controller_type': result.name,
'name': result.name,
'joint_names': list(model.joint_names),
'q_eq': _tolist(model.q_eq),
'actuated_joint_names': list(model.actuated_joint_names),
'u_eq': _tolist(model.u_eq),
'output_names': list(model.output_names),
}
if result.K is not None:
cfg['K'] = _tolist(result.K)
if result.controller is not None:
k = result.controller
cfg['ctrl_A'] = _tolist(k.A)
cfg['ctrl_B'] = _tolist(k.B)
cfg['ctrl_C'] = _tolist(k.C)
cfg['ctrl_D'] = _tolist(k.D)
info = _info_dict(result.info)
if info:
cfg['info'] = info
return cfg