Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Path: blob/master/Tools/scripts/extract_param_defaults.py
Views: 1798
#!/usr/bin/env python312'''3Extracts parameter default values from an ArduPilot .bin log file.45Supports Mission Planner, MAVProxy and QGCS file format output67Currently has 95% unit test coverage89AP_FLAKE8_CLEAN1011Amilcar do Carmo Lucas, IAV GmbH12'''1314import argparse15import re16from typing import Dict, Tuple17from pymavlink import mavutil1819NO_DEFAULT_VALUES_MESSAGE = "The .bin file contained no parameter default values. Update to a newer ArduPilot firmware version"20PARAM_NAME_REGEX = r'^[A-Z][A-Z_0-9]*$'21PARAM_NAME_MAX_LEN = 1622MAVLINK_SYSID_MAX = 2**2423MAVLINK_COMPID_MAX = 2**8242526def parse_arguments(args=None):27"""28Parses command line arguments for the script.2930Args:31args: List of command line arguments. Defaults to None, which means it uses sys.argv.3233Returns:34Namespace object containing the parsed arguments.35"""36parser = argparse.ArgumentParser(description='Extracts parameter default values from an ArduPilot .bin log file.')37parser.add_argument('-f', '--format',38choices=['missionplanner', 'mavproxy', 'qgcs'],39default='missionplanner', help='Output file format. Defaults to %(default)s.',40)41parser.add_argument('-s', '--sort',42choices=['none', 'missionplanner', 'mavproxy', 'qgcs'],43default='', help='Sort the parameters in the file. Defaults to the same as the format.',44)45parser.add_argument('-v', '--version', action='version', version='%(prog)s 1.0',46help='Display version information and exit.',47)48parser.add_argument('-i', '--sysid', type=int, default=-1,49help='System ID for qgcs output format. Defaults to SYSID_THISMAV if defined else 1.',50)51parser.add_argument('-c', '--compid', type=int, default=-1,52help='Component ID for qgcs output format. Defaults to 1.',53)54parser.add_argument('bin_file', help='The ArduPilot .bin log file to read')55args, _ = parser.parse_known_args(args)5657if args.sort == '':58args.sort = args.format5960if args.format != 'qgcs':61if args.sysid != -1:62raise SystemExit("--sysid parameter is only relevant if --format is qgcs")63if args.compid != -1:64raise SystemExit("--compid parameter is only relevant if --format is qgcs")6566return args676869def extract_parameter_default_values(logfile: str) -> Dict[str, float]:70"""71Extracts the parameter default values from an ArduPilot .bin log file.7273Args:74logfile: The path to the ArduPilot .bin log file.7576Returns:77A dictionary with parameter names as keys and their default values as float.78"""79try:80mlog = mavutil.mavlink_connection(logfile)81except Exception as e:82raise SystemExit("Error opening the %s logfile: %s" % (logfile, str(e))) from e83defaults = {}84while True:85m = mlog.recv_match(type=['PARM'])86if m is None:87if not defaults:88raise SystemExit(NO_DEFAULT_VALUES_MESSAGE)89return defaults90pname = m.Name91if len(pname) > PARAM_NAME_MAX_LEN:92raise SystemExit("Too long parameter name: %s" % pname)93if not re.match(PARAM_NAME_REGEX, pname):94raise SystemExit("Invalid parameter name %s" % pname)95# parameter names are supposed to be unique96if pname not in defaults and hasattr(m, 'Default'):97defaults[pname] = m.Default # the first time default is declared is enough9899100def missionplanner_sort(item: str) -> Tuple[str, ...]:101"""102Sorts a parameter name according to the rules defined in the Mission Planner software.103104Args:105item: The parameter name to sort.106107Returns:108A tuple representing the sorted parameter name.109"""110parts = item.split("_") # Split the parameter name by underscore111# Compare the parts separately112return tuple(parts)113114115def mavproxy_sort(item: str) -> str:116"""117Sorts a parameter name according to the rules defined in the MAVProxy software.118119Args:120item: The parameter name to sort.121122Returns:123The sorted parameter name.124"""125return item126127128def sort_params(defaults: Dict[str, float], sort_type: str = 'none') -> Dict[str, float]:129"""130Sorts parameter names according to sort_type131132Args:133defaults: A dictionary with parameter names as keys and their default values as float.134sort_type: The type of sorting to apply. Can be 'none', 'missionplanner', 'mavproxy' or 'qgcs'.135136Returns:137A dictionary with parameter names as keys and their default values as float.138"""139if sort_type == "missionplanner":140defaults = dict(sorted(defaults.items(), key=lambda x: missionplanner_sort(x[0])))141elif sort_type == "mavproxy":142defaults = dict(sorted(defaults.items(), key=lambda x: mavproxy_sort(x[0])))143elif sort_type == "qgcs":144defaults = {k: defaults[k] for k in sorted(defaults)}145return defaults146147148def output_params(defaults: Dict[str, float], format_type: str = 'missionplanner',149sysid: int = -1, compid: int = -1) -> None:150"""151Outputs parameters names and their default values to the console152153Args:154defaults: A dictionary with parameter names as keys and their default values as float.155format_type: The output file format. Can be 'missionplanner', 'mavproxy' or 'qgcs'.156157Returns:158None159"""160if format_type == "qgcs":161if sysid == -1:162if 'SYSID_THISMAV' in defaults:163sysid = defaults['SYSID_THISMAV']164else:165sysid = 1 # if unspecified, default to 1166if compid == -1:167compid = 1 # if unspecified, default to 1168if sysid < 0:169raise SystemExit("Invalid system ID parameter %i must not be negative" % sysid)170if sysid > MAVLINK_SYSID_MAX-1:171raise SystemExit("Invalid system ID parameter %i must be smaller than %i" % (sysid, MAVLINK_SYSID_MAX))172if compid < 0:173raise SystemExit("Invalid component ID parameter %i must not be negative" % compid)174if compid > MAVLINK_COMPID_MAX-1:175raise SystemExit("Invalid component ID parameter %i must be smaller than %i" % (compid, MAVLINK_COMPID_MAX))176# see https://dev.qgroundcontrol.com/master/en/file_formats/parameters.html177print("""178# # Vehicle-Id Component-Id Name Value Type179""")180181for param_name, default_value in defaults.items():182if format_type == "missionplanner":183try:184default_value = format(default_value, '.6f').rstrip('0').rstrip('.')185except ValueError:186pass # preserve non-floating point strings, if present187print(f"{param_name},{default_value}")188elif format_type == "mavproxy":189print("%-15s %.6f" % (param_name, default_value))190elif format_type == "qgcs":191MAV_PARAM_TYPE_REAL32 = 9192print("%u %u %-15s %.6f %u" %193(sysid, compid, param_name, default_value, MAV_PARAM_TYPE_REAL32))194195196def main():197args = parse_arguments()198parameter_default_values = extract_parameter_default_values(args.bin_file)199parameter_default_values = sort_params(parameter_default_values, args.sort)200output_params(parameter_default_values, args.format, args.sysid, args.compid)201202203if __name__ == "__main__":204main()205206207