Skip to content

Compiled Controller

CompiledController

Controller class to handle using compiled controllers. This class expects that your function has the form: myFunction(inputs, outputs) where inputs is a pointer to an inputs structure and outputs is a pointer to an outputs structure. You can define these input and output structures however you please. See examples folder of repo for examples.

Parameters:

Name Type Description Default
library_name string

The name of the compiled library file, without the *.so

required
library_path string

The path to the directory containing the library. See examples for how to get working directory of parent script.

required
main_function_name string

Name of the main function to call within the library. This is the function that will get called via the run() method initialization_function_name (string): Name of an initialization function for your library. This gets called only once when the library is loaded. If you don't have an initialization function, pass None.

required
cleanup_function_name string

Name of a cleanup function for your library. This gets called when the CompiledController class has gone out of scope and is garbage collected. Again, pass None if you don't need this functionality.

None
Authors

Kevin Best, Senthur Raj Ayyappan Neurobionics Lab Robotics Department University of Michigan October 2023

Source code in opensourceleg/control/compiled.py
class CompiledController:
    """
    Controller class to handle using compiled controllers.
    This class expects that your function has the form: myFunction(*inputs, *outputs)
    where *inputs is a pointer to an inputs structure and
    *outputs is a pointer to an outputs structure.
    You can define these input and output structures however you please.
    See examples folder of repo for examples.

    Args:
        library_name (string): The name of the compiled library file, without the *.so
        library_path (string): The path to the directory containing the library.
            See examples for how to get working directory of parent script.
        main_function_name (string): Name of the main function to call within the library.
            This is the function that will get called via the run() method initialization_function_name (string): Name
            of an initialization function for your library. This gets called only once when the library is loaded. If
            you don't have an initialization function, pass None.
        cleanup_function_name (string): Name of a cleanup function for your library.
            This gets called when the CompiledController class has gone out of scope and is garbage collected.
            Again, pass None if you don't need this functionality.

    Authors:
        Kevin Best, Senthur Raj Ayyappan
        Neurobionics Lab
        Robotics Department
        University of Michigan
        October 2023
    """

    def __init__(
        self,
        library_name: str,
        library_path: str,
        main_function_name: str,
        initialization_function_name: Optional[str] = None,
        cleanup_function_name: Optional[str] = None,
    ) -> None:
        self.lib: Any = ctl.load_library(library_name, library_path)
        self.cleanup_func: Callable = self._load_function(cleanup_function_name)
        self.main_function: Callable = self._load_function(main_function_name)
        self.init_function: Callable = self._load_function(initialization_function_name)
        # Note if requested function name is None, returned handle is also none

        if self.init_function is not None:
            self.init_function()

        # This alias makes defining types from top script easier without second import
        self.types = ctypes

        self.DEFAULT_SENSOR_LIST = [
            ("knee_angle", self.types.c_double),
            ("ankle_angle", self.types.c_double),
            ("knee_velocity", self.types.c_double),
            ("ankle_velocity", self.types.c_double),
            ("Fz", self.types.c_double),
        ]

        self._input_type: Optional[type[ctypes.Structure]] = None
        self.inputs: Optional[ctypes.Structure] = None
        self._output_type: Optional[type[ctypes.Structure]] = None
        self.outputs: Optional[ctypes.Structure] = None

    def __del__(self) -> None:
        if hasattr(self, "cleanup_func") and self.cleanup_func is not None:
            self.cleanup_func()

    def __repr__(self) -> str:
        return "CompiledController"

    def _load_function(self, function_name: Optional[str]) -> Any:
        if function_name is None:
            return None
        else:
            try:
                function_handle = getattr(self.lib, function_name)
            except AttributeError:
                LOGGER.warning(f"Function {function_name} not found in library {self.lib}")
            return function_handle

    def _register_type(self, name: str, typ: type[ctypes.Structure]) -> None:
        """Register a ctypes Structure type on the `types` namespace.

        Use setattr to avoid static attribute checks on the underlying module object.
        """
        setattr(self.types, name, typ)

    def define_inputs(
        self, input_list: list[Any] | None = None, input_type: type[ctypes.Structure] | None = None
    ) -> None:
        """
        This method defines the input structure to your function.
        Provide either and input_list or an input_type.
        See example folder and tutorials for help on using this method.

        Parameters
        -----------
        input_list: A list of [('field_name', field_type)...] defining the input structure.
            Use this when you want to define a new ctypes type to use as the input.
            field_name is a string you choose as the title of the field.
            field_type is a type either given by a native c_types value or
                a custom type defined via the define_type() method.
                All types can be accessed as CompiledController.types.(type_name)
        input_type: A ctypes.Structure class reference to use directly as the input type.
            Use this if you have a ctype object already defined.

        Raises
        ------
        ValueError: If both input_list and input_type are provided, or if neither is provided.
        """
        if input_type is not None and input_list is not None:
            raise ValueError("Only one of input_list or input_type should be provided.")
        elif input_type is not None:
            self._input_type = input_type
            self._register_type("inputs", input_type)
        elif input_list is not None:
            self._input_type = self.define_type("inputs", input_list)
        else:
            raise ValueError("Must provide either input_list or input_type.")

        if self._input_type is None:
            raise ValueError("Input type not defined properly. Check define_type() method.")

        self.inputs = self._input_type()

    def define_outputs(
        self, output_list: list[Any] | None = None, output_type: type[ctypes.Structure] | None = None
    ) -> None:
        """
        This method defines the output structure to your function.
        Provide either an output_list or an output_type.
        See example folder and tutorials for help on using this method.

        Parameters
        -----------
        output_list: A list of [('field_name', field_type)...] defining the output structure.
            field_name is a string you choose as the title of the field.
            field_type is a type either given by a native c_types value or
                a custom type defined via the define_type() method.
                All types can be accessed as CompiledController.types.(type_name)
        output_type: A ctypes.Structure class reference to use directly as the output type.
            Use this if you have a ctype object already defined somewhere.

        Raises
        ------
        ValueError: If both output_list and output_type are provided, or if neither is provided.
        """
        if output_type is not None and output_list is not None:
            raise ValueError("Only one of output_list or output_type should be provided.")
        elif output_type is not None:
            self._output_type = output_type
            self._register_type("outputs", output_type)
        elif output_list is not None:
            self._output_type = self.define_type("outputs", output_list)
        else:
            raise ValueError("Must provide either output_list or output_type.")

        if self._output_type is None:
            raise ValueError("Output type not defined properly. Check define_type() method.")

        self.outputs = self._output_type()

    def define_type(self, type_name: str, parameter_list: list[Any]) -> Any:
        """
        This method defines a new type to be used in the compiled controller.
        After calling this method, the datatype with name type_name will be
        available in my_controller.types.type_name for use.
        See example folder and tutorials for help on using this method.

        Parameters
        ------------
        type_name : A string defining the name of your new datatype
        parameter_list: A list of [('field_name', field_type)...]
            field_name is a string you choose as the title of the field.
            field_type is a type either given by a native c_types value or
                a custom type defined via the define_type() method.
                All types can be accessed as CompiledController.types.(type_name)

        Example Usage
        ------------
            my_controller.DefineType('vector3D', [('x', my_controller.types.c_double),
                                                  ('y', my_controller.types.c_double),
                                                  ('z', my_controller.types.c_double)])
        """
        slots = []
        for param in parameter_list:
            slots.append(param[0])

        class CustomStructure(ctypes.Structure):
            _fields_ = parameter_list
            __slots__ = slots

        self._register_type(type_name, CustomStructure)
        return getattr(self.types, type_name)

    def run(self) -> Any:
        """
        This method calls the main controller function of the library.
        Under the hood, it calls library_name.main_function_name(*inputs, *outputs),
        where library_name and main_function_name were given in the constructor.

        Parameters -> None

        Returns:
            The output structure as defined by the define_outputs() method.

        Raises:
            ValueError: If define_inputs() or define_outputs() have not been called.
        """
        if self.inputs is None:
            raise ValueError("Must define input type before calling controller.run(). Use define_inputs() method.")
        if self.outputs is None:
            raise ValueError("Must define output type before calling controller.run(). Use define_outputs() method.")
        self.main_function(ctypes.byref(self.inputs), ctypes.byref(self.outputs))
        return self.outputs

define_inputs(input_list=None, input_type=None)

This method defines the input structure to your function. Provide either and input_list or an input_type. See example folder and tutorials for help on using this method.

Parameters

input_list: A list of [('field_name', field_type)...] defining the input structure. Use this when you want to define a new ctypes type to use as the input. field_name is a string you choose as the title of the field. field_type is a type either given by a native c_types value or a custom type defined via the define_type() method. All types can be accessed as CompiledController.types.(type_name) input_type: A ctypes.Structure class reference to use directly as the input type. Use this if you have a ctype object already defined.

Raises

ValueError: If both input_list and input_type are provided, or if neither is provided.

Source code in opensourceleg/control/compiled.py
def define_inputs(
    self, input_list: list[Any] | None = None, input_type: type[ctypes.Structure] | None = None
) -> None:
    """
    This method defines the input structure to your function.
    Provide either and input_list or an input_type.
    See example folder and tutorials for help on using this method.

    Parameters
    -----------
    input_list: A list of [('field_name', field_type)...] defining the input structure.
        Use this when you want to define a new ctypes type to use as the input.
        field_name is a string you choose as the title of the field.
        field_type is a type either given by a native c_types value or
            a custom type defined via the define_type() method.
            All types can be accessed as CompiledController.types.(type_name)
    input_type: A ctypes.Structure class reference to use directly as the input type.
        Use this if you have a ctype object already defined.

    Raises
    ------
    ValueError: If both input_list and input_type are provided, or if neither is provided.
    """
    if input_type is not None and input_list is not None:
        raise ValueError("Only one of input_list or input_type should be provided.")
    elif input_type is not None:
        self._input_type = input_type
        self._register_type("inputs", input_type)
    elif input_list is not None:
        self._input_type = self.define_type("inputs", input_list)
    else:
        raise ValueError("Must provide either input_list or input_type.")

    if self._input_type is None:
        raise ValueError("Input type not defined properly. Check define_type() method.")

    self.inputs = self._input_type()

define_outputs(output_list=None, output_type=None)

This method defines the output structure to your function. Provide either an output_list or an output_type. See example folder and tutorials for help on using this method.

Parameters

output_list: A list of [('field_name', field_type)...] defining the output structure. field_name is a string you choose as the title of the field. field_type is a type either given by a native c_types value or a custom type defined via the define_type() method. All types can be accessed as CompiledController.types.(type_name) output_type: A ctypes.Structure class reference to use directly as the output type. Use this if you have a ctype object already defined somewhere.

Raises

ValueError: If both output_list and output_type are provided, or if neither is provided.

Source code in opensourceleg/control/compiled.py
def define_outputs(
    self, output_list: list[Any] | None = None, output_type: type[ctypes.Structure] | None = None
) -> None:
    """
    This method defines the output structure to your function.
    Provide either an output_list or an output_type.
    See example folder and tutorials for help on using this method.

    Parameters
    -----------
    output_list: A list of [('field_name', field_type)...] defining the output structure.
        field_name is a string you choose as the title of the field.
        field_type is a type either given by a native c_types value or
            a custom type defined via the define_type() method.
            All types can be accessed as CompiledController.types.(type_name)
    output_type: A ctypes.Structure class reference to use directly as the output type.
        Use this if you have a ctype object already defined somewhere.

    Raises
    ------
    ValueError: If both output_list and output_type are provided, or if neither is provided.
    """
    if output_type is not None and output_list is not None:
        raise ValueError("Only one of output_list or output_type should be provided.")
    elif output_type is not None:
        self._output_type = output_type
        self._register_type("outputs", output_type)
    elif output_list is not None:
        self._output_type = self.define_type("outputs", output_list)
    else:
        raise ValueError("Must provide either output_list or output_type.")

    if self._output_type is None:
        raise ValueError("Output type not defined properly. Check define_type() method.")

    self.outputs = self._output_type()

define_type(type_name, parameter_list)

This method defines a new type to be used in the compiled controller. After calling this method, the datatype with name type_name will be available in my_controller.types.type_name for use. See example folder and tutorials for help on using this method.

Parameters

type_name : A string defining the name of your new datatype parameter_list: A list of [('field_name', field_type)...] field_name is a string you choose as the title of the field. field_type is a type either given by a native c_types value or a custom type defined via the define_type() method. All types can be accessed as CompiledController.types.(type_name)

Example Usage
my_controller.DefineType('vector3D', [('x', my_controller.types.c_double),
                                      ('y', my_controller.types.c_double),
                                      ('z', my_controller.types.c_double)])
Source code in opensourceleg/control/compiled.py
def define_type(self, type_name: str, parameter_list: list[Any]) -> Any:
    """
    This method defines a new type to be used in the compiled controller.
    After calling this method, the datatype with name type_name will be
    available in my_controller.types.type_name for use.
    See example folder and tutorials for help on using this method.

    Parameters
    ------------
    type_name : A string defining the name of your new datatype
    parameter_list: A list of [('field_name', field_type)...]
        field_name is a string you choose as the title of the field.
        field_type is a type either given by a native c_types value or
            a custom type defined via the define_type() method.
            All types can be accessed as CompiledController.types.(type_name)

    Example Usage
    ------------
        my_controller.DefineType('vector3D', [('x', my_controller.types.c_double),
                                              ('y', my_controller.types.c_double),
                                              ('z', my_controller.types.c_double)])
    """
    slots = []
    for param in parameter_list:
        slots.append(param[0])

    class CustomStructure(ctypes.Structure):
        _fields_ = parameter_list
        __slots__ = slots

    self._register_type(type_name, CustomStructure)
    return getattr(self.types, type_name)

run()

This method calls the main controller function of the library. Under the hood, it calls library_name.main_function_name(inputs, outputs), where library_name and main_function_name were given in the constructor.

Parameters -> None

Returns:

Type Description
Any

The output structure as defined by the define_outputs() method.

Raises:

Type Description
ValueError

If define_inputs() or define_outputs() have not been called.

Source code in opensourceleg/control/compiled.py
def run(self) -> Any:
    """
    This method calls the main controller function of the library.
    Under the hood, it calls library_name.main_function_name(*inputs, *outputs),
    where library_name and main_function_name were given in the constructor.

    Parameters -> None

    Returns:
        The output structure as defined by the define_outputs() method.

    Raises:
        ValueError: If define_inputs() or define_outputs() have not been called.
    """
    if self.inputs is None:
        raise ValueError("Must define input type before calling controller.run(). Use define_inputs() method.")
    if self.outputs is None:
        raise ValueError("Must define output type before calling controller.run(). Use define_outputs() method.")
    self.main_function(ctypes.byref(self.inputs), ctypes.byref(self.outputs))
    return self.outputs