-
Notifications
You must be signed in to change notification settings - Fork 17.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tools: scripts: Update MAVLink parsing for wiki inclusion #27226
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,7 @@ | |
from dataclasses import dataclass, astuple | ||
|
||
from pymavlink.dialects.v20 import ( | ||
common, icarous, cubepilot, uAvionix, ardupilotmega | ||
common, icarous, cubepilot, uAvionix, ardupilotmega, development | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing minimal.xml and/or parsing of include files and use all.xml? Check Wiki to see if HEARTBEAT is missing? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Copying part of my response to Tridge from above:
Not sure what you're referring to here - this script imports the dialects from pymavlink; it doesn't know about the underlying xml files, and just checks against which messages pymavlink knows about. In the case of sub-sets (like common < ardupilotmega), as long as the checks are performed in the correct order the relevant dialect is determined as the first match, and can be used to generate a suitable link to the MAVLink docs. It would be nice to directly query the |
||
) | ||
|
||
class MAVLinkDialect(StrEnum): | ||
|
@@ -18,6 +18,7 @@ class MAVLinkDialect(StrEnum): | |
CUBEPILOT = 'cubepilot' | ||
UAVIONIX = 'uAvionix' | ||
ARDUPILOTMEGA = 'ardupilotmega' | ||
DEVELOPMENT = 'development' | ||
UNKNOWN = 'UNKNOWN' | ||
|
||
|
||
|
@@ -152,7 +153,8 @@ class MAVLinkDetector: | |
ARDUPILOT_URL = 'https://github.com/ArduPilot/ardupilot/tree/{branch}/{source}' | ||
EXPORT_FILETYPES = { | ||
'csv': 'csv', | ||
'markdown': 'md' | ||
'markdown': 'md', | ||
'rst': 'rst', | ||
} | ||
|
||
MARKDOWN_INTRO = ( | ||
|
@@ -170,6 +172,9 @@ class MAVLinkDetector: | |
' to do something meaningful with it.{unsupported}{stream_groups}' | ||
) | ||
|
||
# Convert markdown hyperlinks into rst syntax | ||
RST_INTRO = MARKDOWN_INTRO.replace('[', '`').replace('](', ' <').replace(')', '>`_') | ||
|
||
VEHICLES = ('AntennaTracker', 'ArduCopter', 'ArduPlane', 'ArduSub', 'Rover') | ||
|
||
def __init__(self, common_files, vehicle='ALL', | ||
|
@@ -193,7 +198,12 @@ def __init__(self, common_files, vehicle='ALL', | |
folder = file.parent.stem | ||
if folder in exclude_libraries: | ||
continue | ||
text = file.read_text() | ||
|
||
try: | ||
text = file.read_text() | ||
except FileNotFoundError: # Broken symlink | ||
continue | ||
Comment on lines
+204
to
+205
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why would we ignore this?! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good question. I remember having a reason for it back when I added it, but not exactly what that was. My vague recollection is it was related to an unused ardupilot library import that wasn't linked to / removed properly in one of the vehicle branches (possibly Sub?). Whatever it was was stopping the parsing from completing, and seemed out of scope for this script to solve, so I added the check and moved on. If you want more context I can try removing the check and re-running the script for every firmware type and version, to see if I can replicate the original issue? |
||
|
||
source = f'{folder}/{file.name}' | ||
if file == self.COMMON_FILE: | ||
for mavlink, ap_message in self.find_requestable_messages(text): | ||
|
@@ -262,7 +272,11 @@ def find_named_ints(cls, text: str): | |
def get_stream_groups(self, vehicle): | ||
stream_groups = ['stream_groups'] | ||
|
||
text = (self.BASE_DIR / vehicle / self.STREAM_GROUP_FILE).read_text() | ||
try: | ||
text = (self.BASE_DIR / vehicle / self.STREAM_GROUP_FILE).read_text() | ||
except FileNotFoundError: # No stream groups | ||
return [] | ||
|
||
for group_name, message_data in self.STREAM_GROUPS.findall(text): | ||
stream_groups.extend(sorted( | ||
MAVLinkMessage(self._ap_to_mavlink.get(ap_message, ap_message), | ||
|
@@ -422,6 +436,74 @@ def export_markdown(self, file, iterable, branch='master', header=None, | |
|
||
print(name, source, dialect, sep=' | ', file=file) | ||
|
||
def export_rst(self, file, iterable, branch='master', header=None, | ||
use_intro=True, **extra_kwargs): | ||
if header == 'ardupilot_wiki': | ||
header = '\n'.join(( | ||
'.. _mavlink_support:', | ||
'', | ||
'===============', | ||
'MAVLink Support', | ||
'===============', | ||
'\n', | ||
)) | ||
|
||
if header: | ||
print(header, file=file) | ||
|
||
if use_intro: | ||
commands = stream_groups = unsupported = '' | ||
if extra_kwargs['include_commands']: | ||
commands = ' (and commands)' | ||
if extra_kwargs['include_unsupported']: | ||
unsupported = ( | ||
'\n\nKnown :ref:`unsupported messages <mavlink_missing_messages>`' | ||
f'{commands} are shown at the end.' | ||
) | ||
if extra_kwargs['include_stream_groups']: | ||
stream_groups = ( | ||
'\n\nThe autopilot includes a set of :ref:`mavlink_stream_groups`' | ||
' for convenience, which allow configuring the stream rates of' | ||
' groups of requestable messages by setting parameter values. ' | ||
'It is also possible to manually request messages, and request' | ||
' individual messages be streamed at a specified rate.' | ||
) | ||
vehicle = self.vehicle.replace('ALL', 'ArduPilot') | ||
|
||
print(self.RST_INTRO.format( | ||
vehicle=vehicle, commands=commands, | ||
stream_groups=stream_groups, unsupported=unsupported | ||
), '\n', file=file) | ||
|
||
for data in iterable: | ||
match data: | ||
case str() as type_: | ||
reference = f'mavlink_{type_}' | ||
heading = type_.title().replace('_', ' ') | ||
source_header = ( | ||
'Code Source' if type_ != 'stream_groups' else | ||
'Stream Group Parameter' | ||
) | ||
print(f'\n.. _{reference}:\n\n{heading}\n{"="*len(heading)}\n', | ||
self.get_description(type_).replace('`','``'), | ||
'\n.. csv-table::', | ||
f' :header: MAVLink Message, {source_header}, MAVLink Dialect\n\n', | ||
sep='\n', file=file) | ||
case MAVLinkMessage() as message: | ||
name, source, dialect = message.as_tuple() | ||
if dialect != MAVLinkDialect.UNKNOWN: | ||
msg_url = self.MAVLINK_URL.format(dialect=dialect, | ||
message_name=name.split(':')[0]) | ||
name = f'`{name} <{msg_url}>`_' | ||
if source != 'UNSUPPORTED' and not source.startswith('SRn'): | ||
folder = source.split('/')[0] | ||
base = 'libraries/' if folder not in self.VEHICLES else '' | ||
code_url = self.ARDUPILOT_URL.format(branch=branch, | ||
source=base+source) | ||
source = f'`{source} <{code_url}>`_' | ||
|
||
print(f' {name}', source, dialect, sep=', ', file=file) | ||
|
||
|
||
if __name__ == '__main__': | ||
from inspect import signature | ||
|
@@ -431,6 +513,7 @@ def export_markdown(self, file, iterable, branch='master', header=None, | |
default_vehicle = detector_init_params['vehicle'].default | ||
vehicle_options = [default_vehicle, *MAVLinkDetector.VEHICLES] | ||
default_exclusions = detector_init_params['exclude_libraries'].default | ||
format_options = [*MAVLinkDetector.EXPORT_FILETYPES, 'none'] | ||
|
||
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) | ||
parse_opts = parser.add_argument_group('parsing options') | ||
|
@@ -449,7 +532,7 @@ def export_markdown(self, file, iterable, branch='master', header=None, | |
export_opts.add_argument('-q', '--quiet', action='store_true', | ||
help='Disable printout, only export a file.') | ||
export_opts.add_argument('-f', '--format', default='markdown', | ||
choices=['csv', 'markdown', 'none'], | ||
choices=format_options, | ||
help='Desired format for the exported file.') | ||
export_opts.add_argument('-b', '--branch', | ||
help=('The branch to link to in markdown mode.' | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be just be 'all'? either that or needs minimal
does this end up with lots of duplicates via the recursion of ardupilot -> common -> minimal?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's nice to break them out into dialects, so that the message definitions can be linked to directly within the MAVLink documentation.
The messages defined in
minimal
are duplicated in thecommon
doc, so I didn't think it was necessary to break it out further (because the links already work fine).Happy to make that change if you think it's preferable to link to the minimal docs instead of the common docs for those couple of messages. Thoughts?
Nope - the dialects are checked in order (see
determine_dialect
), so it just stops when it gets to the first one that matches. TheMAVLinkDetector
class maps relevant messages to their dialects (and corresponding documentation links) as needed, so the only message duplicates are if the firmware handles the same message in different ways that are checked for (e.g.SYSTEM_TIME
can be incoming, (automatically) outgoing, and also requestable, so appears in all three tables).