CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
Ardupilot

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.

GitHub Repository: Ardupilot/ardupilot
Path: blob/master/Tools/scripts/extract_param_defaults.py
Views: 1798
1
#!/usr/bin/env python3
2
3
'''
4
Extracts parameter default values from an ArduPilot .bin log file.
5
6
Supports Mission Planner, MAVProxy and QGCS file format output
7
8
Currently has 95% unit test coverage
9
10
AP_FLAKE8_CLEAN
11
12
Amilcar do Carmo Lucas, IAV GmbH
13
'''
14
15
import argparse
16
import re
17
from typing import Dict, Tuple
18
from pymavlink import mavutil
19
20
NO_DEFAULT_VALUES_MESSAGE = "The .bin file contained no parameter default values. Update to a newer ArduPilot firmware version"
21
PARAM_NAME_REGEX = r'^[A-Z][A-Z_0-9]*$'
22
PARAM_NAME_MAX_LEN = 16
23
MAVLINK_SYSID_MAX = 2**24
24
MAVLINK_COMPID_MAX = 2**8
25
26
27
def parse_arguments(args=None):
28
"""
29
Parses command line arguments for the script.
30
31
Args:
32
args: List of command line arguments. Defaults to None, which means it uses sys.argv.
33
34
Returns:
35
Namespace object containing the parsed arguments.
36
"""
37
parser = argparse.ArgumentParser(description='Extracts parameter default values from an ArduPilot .bin log file.')
38
parser.add_argument('-f', '--format',
39
choices=['missionplanner', 'mavproxy', 'qgcs'],
40
default='missionplanner', help='Output file format. Defaults to %(default)s.',
41
)
42
parser.add_argument('-s', '--sort',
43
choices=['none', 'missionplanner', 'mavproxy', 'qgcs'],
44
default='', help='Sort the parameters in the file. Defaults to the same as the format.',
45
)
46
parser.add_argument('-v', '--version', action='version', version='%(prog)s 1.0',
47
help='Display version information and exit.',
48
)
49
parser.add_argument('-i', '--sysid', type=int, default=-1,
50
help='System ID for qgcs output format. Defaults to SYSID_THISMAV if defined else 1.',
51
)
52
parser.add_argument('-c', '--compid', type=int, default=-1,
53
help='Component ID for qgcs output format. Defaults to 1.',
54
)
55
parser.add_argument('bin_file', help='The ArduPilot .bin log file to read')
56
args, _ = parser.parse_known_args(args)
57
58
if args.sort == '':
59
args.sort = args.format
60
61
if args.format != 'qgcs':
62
if args.sysid != -1:
63
raise SystemExit("--sysid parameter is only relevant if --format is qgcs")
64
if args.compid != -1:
65
raise SystemExit("--compid parameter is only relevant if --format is qgcs")
66
67
return args
68
69
70
def extract_parameter_default_values(logfile: str) -> Dict[str, float]:
71
"""
72
Extracts the parameter default values from an ArduPilot .bin log file.
73
74
Args:
75
logfile: The path to the ArduPilot .bin log file.
76
77
Returns:
78
A dictionary with parameter names as keys and their default values as float.
79
"""
80
try:
81
mlog = mavutil.mavlink_connection(logfile)
82
except Exception as e:
83
raise SystemExit("Error opening the %s logfile: %s" % (logfile, str(e))) from e
84
defaults = {}
85
while True:
86
m = mlog.recv_match(type=['PARM'])
87
if m is None:
88
if not defaults:
89
raise SystemExit(NO_DEFAULT_VALUES_MESSAGE)
90
return defaults
91
pname = m.Name
92
if len(pname) > PARAM_NAME_MAX_LEN:
93
raise SystemExit("Too long parameter name: %s" % pname)
94
if not re.match(PARAM_NAME_REGEX, pname):
95
raise SystemExit("Invalid parameter name %s" % pname)
96
# parameter names are supposed to be unique
97
if pname not in defaults and hasattr(m, 'Default'):
98
defaults[pname] = m.Default # the first time default is declared is enough
99
100
101
def missionplanner_sort(item: str) -> Tuple[str, ...]:
102
"""
103
Sorts a parameter name according to the rules defined in the Mission Planner software.
104
105
Args:
106
item: The parameter name to sort.
107
108
Returns:
109
A tuple representing the sorted parameter name.
110
"""
111
parts = item.split("_") # Split the parameter name by underscore
112
# Compare the parts separately
113
return tuple(parts)
114
115
116
def mavproxy_sort(item: str) -> str:
117
"""
118
Sorts a parameter name according to the rules defined in the MAVProxy software.
119
120
Args:
121
item: The parameter name to sort.
122
123
Returns:
124
The sorted parameter name.
125
"""
126
return item
127
128
129
def sort_params(defaults: Dict[str, float], sort_type: str = 'none') -> Dict[str, float]:
130
"""
131
Sorts parameter names according to sort_type
132
133
Args:
134
defaults: A dictionary with parameter names as keys and their default values as float.
135
sort_type: The type of sorting to apply. Can be 'none', 'missionplanner', 'mavproxy' or 'qgcs'.
136
137
Returns:
138
A dictionary with parameter names as keys and their default values as float.
139
"""
140
if sort_type == "missionplanner":
141
defaults = dict(sorted(defaults.items(), key=lambda x: missionplanner_sort(x[0])))
142
elif sort_type == "mavproxy":
143
defaults = dict(sorted(defaults.items(), key=lambda x: mavproxy_sort(x[0])))
144
elif sort_type == "qgcs":
145
defaults = {k: defaults[k] for k in sorted(defaults)}
146
return defaults
147
148
149
def output_params(defaults: Dict[str, float], format_type: str = 'missionplanner',
150
sysid: int = -1, compid: int = -1) -> None:
151
"""
152
Outputs parameters names and their default values to the console
153
154
Args:
155
defaults: A dictionary with parameter names as keys and their default values as float.
156
format_type: The output file format. Can be 'missionplanner', 'mavproxy' or 'qgcs'.
157
158
Returns:
159
None
160
"""
161
if format_type == "qgcs":
162
if sysid == -1:
163
if 'SYSID_THISMAV' in defaults:
164
sysid = defaults['SYSID_THISMAV']
165
else:
166
sysid = 1 # if unspecified, default to 1
167
if compid == -1:
168
compid = 1 # if unspecified, default to 1
169
if sysid < 0:
170
raise SystemExit("Invalid system ID parameter %i must not be negative" % sysid)
171
if sysid > MAVLINK_SYSID_MAX-1:
172
raise SystemExit("Invalid system ID parameter %i must be smaller than %i" % (sysid, MAVLINK_SYSID_MAX))
173
if compid < 0:
174
raise SystemExit("Invalid component ID parameter %i must not be negative" % compid)
175
if compid > MAVLINK_COMPID_MAX-1:
176
raise SystemExit("Invalid component ID parameter %i must be smaller than %i" % (compid, MAVLINK_COMPID_MAX))
177
# see https://dev.qgroundcontrol.com/master/en/file_formats/parameters.html
178
print("""
179
# # Vehicle-Id Component-Id Name Value Type
180
""")
181
182
for param_name, default_value in defaults.items():
183
if format_type == "missionplanner":
184
try:
185
default_value = format(default_value, '.6f').rstrip('0').rstrip('.')
186
except ValueError:
187
pass # preserve non-floating point strings, if present
188
print(f"{param_name},{default_value}")
189
elif format_type == "mavproxy":
190
print("%-15s %.6f" % (param_name, default_value))
191
elif format_type == "qgcs":
192
MAV_PARAM_TYPE_REAL32 = 9
193
print("%u %u %-15s %.6f %u" %
194
(sysid, compid, param_name, default_value, MAV_PARAM_TYPE_REAL32))
195
196
197
def main():
198
args = parse_arguments()
199
parameter_default_values = extract_parameter_default_values(args.bin_file)
200
parameter_default_values = sort_params(parameter_default_values, args.sort)
201
output_params(parameter_default_values, args.format, args.sysid, args.compid)
202
203
204
if __name__ == "__main__":
205
main()
206
207